diff --git a/.github/labeler.yml b/.github/labeler.yml index 57c5e1120f..3c0bf473e0 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -29,8 +29,6 @@ internal: - scripts/** - .gitignore - .pre-commit-config.yaml - - pdm_build.py - - requirements*.txt - uv.lock - docs/en/data/sponsors.yml - docs/en/overrides/main.html diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index 77bce055c6..a0b507dd90 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -97,7 +97,7 @@ jobs: path: docs/${{ matrix.lang }}/.cache - name: Build Docs run: uv run ./scripts/docs.py build-lang ${{ matrix.lang }} - - uses: actions/upload-artifact@v6 + - uses: actions/upload-artifact@v7 with: name: docs-site-${{ matrix.lang }} path: ./site/** diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 734fc244d3..3002120399 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -45,7 +45,7 @@ jobs: run: | rm -rf ./site mkdir ./site - - uses: actions/download-artifact@v7 + - uses: actions/download-artifact@v8 with: path: ./site/ pattern: docs-site-* diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2232498cb1..58f4f6dd8a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -8,11 +8,6 @@ on: jobs: publish: runs-on: ubuntu-latest - strategy: - matrix: - package: - - fastapi - - fastapi-slim permissions: id-token: write contents: read @@ -26,14 +21,9 @@ jobs: uses: actions/setup-python@v6 with: 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 uses: astral-sh/setup-uv@v7 - name: Build distribution run: uv build - env: - TIANGOLO_BUILD_PACKAGE: ${{ matrix.package }} - name: Publish run: uv publish diff --git a/.github/workflows/smokeshow.yml b/.github/workflows/smokeshow.yml index f23b962b70..7e7cbc68bf 100644 --- a/.github/workflows/smokeshow.yml +++ b/.github/workflows/smokeshow.yml @@ -28,7 +28,7 @@ jobs: pyproject.toml uv.lock - run: uv sync --locked --no-dev --group github-actions - - uses: actions/download-artifact@v7 + - uses: actions/download-artifact@v8 with: name: coverage-html path: htmlcov diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 338f6c390f..5b139f1d12 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -45,7 +45,7 @@ jobs: test: needs: - changes - if: needs.changes.outputs.src == 'true' + if: needs.changes.outputs.src == 'true' || github.ref == 'refs/heads/master' strategy: matrix: os: [ windows-latest, macos-latest ] @@ -68,10 +68,8 @@ jobs: python-version: "3.13" coverage: coverage uv-resolution: highest - # Ubuntu with 3.13 needs coverage for CodSpeed benchmarks - os: ubuntu-latest python-version: "3.13" - coverage: coverage uv-resolution: highest codspeed: codspeed - os: ubuntu-latest @@ -109,29 +107,52 @@ jobs: run: uv pip install "git+https://github.com/Kludex/starlette@main" - run: mkdir coverage - name: Test - if: matrix.codspeed != 'codspeed' - run: uv run --no-sync bash scripts/test.sh + run: uv run --no-sync bash scripts/test-cov.sh env: COVERAGE_FILE: coverage/.coverage.${{ 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 - name: Store coverage files if: matrix.coverage == 'coverage' - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: coverage-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/coverage/.coverage.*') }} path: coverage 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: needs: - test @@ -155,7 +176,7 @@ jobs: - name: Install Dependencies run: uv sync --locked --no-dev --group tests --extra all - name: Get coverage files - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: pattern: coverage-* path: coverage @@ -164,7 +185,7 @@ jobs: - run: uv run coverage combine coverage - run: uv run coverage html --title "Coverage for ${{ github.sha }}" - name: Store coverage HTML - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: coverage-html path: htmlcov @@ -176,6 +197,7 @@ jobs: if: always() needs: - coverage-combine + - benchmark runs-on: ubuntu-latest steps: - name: Dump GitHub context @@ -186,4 +208,4 @@ jobs: uses: re-actors/alls-green@release/v1 with: jobs: ${{ toJSON(needs) }} - allowed-skips: coverage-combine,test + allowed-skips: coverage-combine,test,benchmark diff --git a/docs/de/docs/advanced/websockets.md b/docs/de/docs/advanced/websockets.md index b1a49c5aad..22c131838e 100644 --- a/docs/de/docs/advanced/websockets.md +++ b/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: -{* ../../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 } 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 @@ -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. -{* ../../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. @@ -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*: -{* ../../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 @@ 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. -{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} +{* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *} Zum Ausprobieren: diff --git a/docs/en/data/contributors.yml b/docs/en/data/contributors.yml index 41115ccbd4..15721587a1 100644 --- a/docs/en/data/contributors.yml +++ b/docs/en/data/contributors.yml @@ -1,13 +1,18 @@ tiangolo: login: tiangolo - count: 871 + count: 922 avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 url: https://github.com/tiangolo dependabot: login: dependabot - count: 133 + count: 142 avatarUrl: https://avatars.githubusercontent.com/in/29110?v=4 url: https://github.com/apps/dependabot +YuriiMotov: + login: YuriiMotov + count: 57 + avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4 + url: https://github.com/YuriiMotov alejsdev: login: alejsdev count: 53 @@ -18,11 +23,6 @@ pre-commit-ci: count: 50 avatarUrl: https://avatars.githubusercontent.com/in/68672?v=4 url: https://github.com/apps/pre-commit-ci -YuriiMotov: - login: YuriiMotov - count: 38 - avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4 - url: https://github.com/YuriiMotov github-actions: login: github-actions count: 26 @@ -33,16 +33,16 @@ Kludex: count: 25 avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4 url: https://github.com/Kludex +svlandeg: + login: svlandeg + count: 18 + avatarUrl: https://avatars.githubusercontent.com/u/8796347?u=556c97650c27021911b0b9447ec55e75987b0e8a&v=4 + url: https://github.com/svlandeg dmontagu: login: dmontagu count: 17 avatarUrl: https://avatars.githubusercontent.com/u/35119617?u=540f30c937a6450812628b9592a1dfe91bbe148e&v=4 url: https://github.com/dmontagu -svlandeg: - login: svlandeg - count: 17 - avatarUrl: https://avatars.githubusercontent.com/u/8796347?u=556c97650c27021911b0b9447ec55e75987b0e8a&v=4 - url: https://github.com/svlandeg nilslindemann: login: nilslindemann count: 15 @@ -148,6 +148,11 @@ AlexWendland: count: 4 avatarUrl: https://avatars.githubusercontent.com/u/3949212?u=c4c0c615e0ea33d00bfe16b779cf6ebc0f58071c&v=4 url: https://github.com/AlexWendland +valentinDruzhinin: + login: valentinDruzhinin + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 + url: https://github.com/valentinDruzhinin divums: login: divums count: 3 @@ -283,11 +288,6 @@ hamidrasti: count: 3 avatarUrl: https://avatars.githubusercontent.com/u/43915620?v=4 url: https://github.com/hamidrasti -valentinDruzhinin: - login: valentinDruzhinin - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 - url: https://github.com/valentinDruzhinin kkinder: login: kkinder count: 2 @@ -521,7 +521,7 @@ s111d: estebanx64: login: estebanx64 count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/10840422?u=812422ae5d6a4bc5ff331c901fc54f9ab3cecf5c&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/10840422?u=2ca073ee47a625e495a9573bd374ddcd7be5ec91&v=4 url: https://github.com/estebanx64 ndimares: login: ndimares @@ -573,3 +573,8 @@ Taoup: count: 2 avatarUrl: https://avatars.githubusercontent.com/u/22348542?v=4 url: https://github.com/Taoup +jonathan-fulton: + login: jonathan-fulton + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/4665111?u=bda1c12e5137bd7771a6aa24d9515b87c11da150&v=4 + url: https://github.com/jonathan-fulton diff --git a/docs/en/data/people.yml b/docs/en/data/people.yml index 2fdb21a059..07094b9cbd 100644 --- a/docs/en/data/people.yml +++ b/docs/en/data/people.yml @@ -1,23 +1,23 @@ maintainers: - login: tiangolo - answers: 1900 + answers: 1925 avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 url: https://github.com/tiangolo experts: - login: tiangolo - count: 1900 + count: 1925 avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 url: https://github.com/tiangolo - login: YuriiMotov - count: 971 - avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 + count: 1120 + avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4 url: https://github.com/YuriiMotov - login: github-actions - count: 769 + count: 770 avatarUrl: https://avatars.githubusercontent.com/in/15368?v=4 url: https://github.com/apps/github-actions - login: Kludex - count: 654 + count: 656 avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4 url: https://github.com/Kludex - login: jgould22 @@ -37,7 +37,7 @@ experts: avatarUrl: https://avatars.githubusercontent.com/u/62724709?u=f1e7bae394a315da950912c92dc861a8eaf95d4c&v=4 url: https://github.com/ycd - login: JarroVGIT - count: 190 + count: 192 avatarUrl: https://avatars.githubusercontent.com/u/13659033?u=e8bea32d07a5ef72f7dde3b2079ceb714923ca05&v=4 url: https://github.com/JarroVGIT - login: euri10 @@ -53,11 +53,11 @@ experts: avatarUrl: https://avatars.githubusercontent.com/u/331403?v=4 url: https://github.com/phy25 - login: JavierSanchezCastro - count: 94 + count: 107 avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 url: https://github.com/JavierSanchezCastro - login: luzzodev - count: 89 + count: 104 avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4 url: https://github.com/luzzodev - login: raphaelauv @@ -81,32 +81,32 @@ experts: avatarUrl: https://avatars.githubusercontent.com/u/653031?u=ad9838e089058c9e5a0bab94c0eec7cc181e0cd0&v=4 url: https://github.com/falkben - login: yinziyan1206 - count: 54 + count: 55 avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4 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 count: 49 avatarUrl: https://avatars.githubusercontent.com/u/516999?u=437c0c5038558c67e887ccd863c1ba0f846c03da&v=4 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 count: 46 avatarUrl: https://avatars.githubusercontent.com/u/1755071?u=612704256e38d6ac9cbed24f10e4b6ac2da74ecb&v=4 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 count: 45 avatarUrl: https://avatars.githubusercontent.com/u/16958893?u=f8be7088d5076d963984a21f95f44e559192d912&v=4 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 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 - login: odiseo0 count: 43 @@ -120,14 +120,14 @@ experts: count: 40 avatarUrl: https://avatars.githubusercontent.com/u/11836741?u=8bd5ef7e62fe6a82055e33c4c0e0a7879ff8cfb6&v=4 url: https://github.com/includeamin -- login: STeveShary - count: 37 - avatarUrl: https://avatars.githubusercontent.com/u/5167622?u=de8f597c81d6336fcebc37b32dfd61a3f877160c&v=4 - url: https://github.com/STeveShary - login: chbndrhnns count: 37 avatarUrl: https://avatars.githubusercontent.com/u/7534547?v=4 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 count: 35 avatarUrl: https://avatars.githubusercontent.com/u/31960541?u=47f4829c77f4962ab437ffb7995951e41eeebe9b&v=4 @@ -136,18 +136,22 @@ experts: count: 32 avatarUrl: https://avatars.githubusercontent.com/u/41326348?u=ba2fda6b30110411ecbf406d187907e2b420ac19&v=4 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 count: 28 avatarUrl: https://avatars.githubusercontent.com/u/28061158?u=6918e39a1224194ba636e897461a02a20126d7ad&v=4 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 - count: 26 + count: 27 avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4 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 count: 26 avatarUrl: https://avatars.githubusercontent.com/u/43723790?u=9d726785d08e50b1e1cd96505800c8ea8405bce2&v=4 @@ -156,10 +160,6 @@ experts: count: 25 avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4 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 count: 23 avatarUrl: https://avatars.githubusercontent.com/u/9435877?u=719327b7d2c4c62212456d771bfa7c6b8dbb9eac&v=4 @@ -176,6 +176,10 @@ experts: count: 22 avatarUrl: https://avatars.githubusercontent.com/u/79946379?u=03d85b22d696a58a9603e55fbbbe2de6b0f4face&v=4 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 count: 21 avatarUrl: https://avatars.githubusercontent.com/u/51059348?u=5fe59a56e1f2f9ccd8005d71752a8276f133ae1a&v=4 @@ -194,7 +198,7 @@ experts: url: https://github.com/ebottos94 - login: estebanx64 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 - login: sehraramiz count: 18 @@ -236,467 +240,471 @@ experts: count: 16 avatarUrl: https://avatars.githubusercontent.com/u/26334101?u=f601c3f111f2148bd9244c2cb3ebbd57b592e674&v=4 url: https://github.com/jonatasoli -- login: ghost - count: 15 - avatarUrl: https://avatars.githubusercontent.com/u/10137?u=b1951d34a583cf12ec0d3b0781ba19be97726318&v=4 - url: https://github.com/ghost -- login: abhint - count: 15 - avatarUrl: https://avatars.githubusercontent.com/u/25699289?u=b5d219277b4d001ac26fb8be357fddd88c29d51b&v=4 - url: https://github.com/abhint -last_month_experts: -- login: YuriiMotov - count: 17 - avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 - url: https://github.com/YuriiMotov -- login: valentinDruzhinin - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 - url: https://github.com/valentinDruzhinin -- login: yinziyan1206 - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4 - url: https://github.com/yinziyan1206 -- login: tiangolo - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 - url: https://github.com/tiangolo -- login: luzzodev - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4 - url: https://github.com/luzzodev -three_months_experts: -- login: YuriiMotov - count: 397 - avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 - 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 - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 - url: https://github.com/tiangolo -- login: sachinh35 - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/21972708?u=8560b97b8b41e175f476270b56de8a493b84f302&v=4 - url: https://github.com/sachinh35 -- login: eqsdxr - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/157279130?u=58fddf77ed76966eaa8c73eea9bea4bb0c53b673&v=4 - url: https://github.com/eqsdxr -- login: Jelle-tenB - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/210023470?u=c25d66addf36a747bd9fab773c4a6e7b238f45d4&v=4 - url: https://github.com/Jelle-tenB -- login: pythonweb2 - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/32141163?v=4 - url: https://github.com/pythonweb2 -- login: WilliamDEdwards - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/12184311?u=9b29d5d1d71f5f1a7ef9e439963ad3529e3b33a4&v=4 - url: https://github.com/WilliamDEdwards -- login: Brikas - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/80290187?v=4 - url: https://github.com/Brikas -- login: purepani - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/7587353?v=4 - url: https://github.com/purepani -- login: JavierSanchezCastro - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 - url: https://github.com/JavierSanchezCastro -- login: TaigoFr - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/17792131?u=372b27056ec82f1ae03d8b3f37ef55b04a7cfdd1&v=4 - url: https://github.com/TaigoFr -- login: Garrett-R - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/6614695?u=c128fd775002882f6e391bda5a89d1bdc5bdf45f&v=4 - url: https://github.com/Garrett-R -- login: jymchng - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/27895426?u=fb88c47775147d62a395fdb895d1af4148c7b566&v=4 - url: https://github.com/jymchng -- login: davidhuser - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/4357648?u=6ed702f8f6d49a8b2a0ed33cbd8ab59c2d7db7f7&v=4 - url: https://github.com/davidhuser -six_months_experts: -- login: YuriiMotov - count: 763 - avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 - 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 - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 - url: https://github.com/tiangolo -- login: JavierSanchezCastro - count: 6 - avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 - url: https://github.com/JavierSanchezCastro -- 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: eqsdxr - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/157279130?u=58fddf77ed76966eaa8c73eea9bea4bb0c53b673&v=4 - url: https://github.com/eqsdxr -- login: Kludex - count: 4 - avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4 - url: https://github.com/Kludex -- login: Jelle-tenB - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/210023470?u=c25d66addf36a747bd9fab773c4a6e7b238f45d4&v=4 - url: https://github.com/Jelle-tenB -- login: adsouza - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/275832?u=f90f110cfafeafed2f14339e840941c2c328c186&v=4 - url: https://github.com/adsouza -- login: pythonweb2 - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/32141163?v=4 - url: https://github.com/pythonweb2 -- login: WilliamDEdwards - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/12184311?u=9b29d5d1d71f5f1a7ef9e439963ad3529e3b33a4&v=4 - url: https://github.com/WilliamDEdwards -- login: Brikas - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/80290187?v=4 - url: https://github.com/Brikas -- login: purepani - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/7587353?v=4 - url: https://github.com/purepani -- login: TaigoFr - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/17792131?u=372b27056ec82f1ae03d8b3f37ef55b04a7cfdd1&v=4 - url: https://github.com/TaigoFr -- login: Garrett-R - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/6614695?u=c128fd775002882f6e391bda5a89d1bdc5bdf45f&v=4 - url: https://github.com/Garrett-R -- login: EverStarck - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/51029456?u=343409b7cb6b3ea6a59359f4e8370d9c3f140ecd&v=4 - url: https://github.com/EverStarck -- login: henrymcl - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/26480299?v=4 - url: https://github.com/henrymcl -- login: jymchng - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/27895426?u=fb88c47775147d62a395fdb895d1af4148c7b566&v=4 - url: https://github.com/jymchng -- login: davidhuser - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/4357648?u=6ed702f8f6d49a8b2a0ed33cbd8ab59c2d7db7f7&v=4 - url: https://github.com/davidhuser -- login: PidgeyBE - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/19860056?u=47b584eb1c1ab45e31c1b474109a962d7e82be49&v=4 - url: https://github.com/PidgeyBE -- login: KianAnbarestani - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/145364424?u=dcc3d8fb4ca07d36fb52a17f38b6650565de40be&v=4 - url: https://github.com/KianAnbarestani -- login: jgould22 - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 - url: https://github.com/jgould22 -- login: marsboy02 - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/86903678?u=04cc319d6605f8d1ba3a0bed9f4f55a582719ae6&v=4 - url: https://github.com/marsboy02 -one_year_experts: -- login: YuriiMotov - count: 824 - avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=b9b13d598dddfab529a52d264df80a900bfe7060&v=4 - url: https://github.com/YuriiMotov -- login: luzzodev - count: 89 - avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4 - url: https://github.com/luzzodev -- login: Kludex - count: 50 - avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4 - url: https://github.com/Kludex -- 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 - count: 24 - avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 - url: https://github.com/valentinDruzhinin -- login: JavierSanchezCastro - count: 24 - avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 - url: https://github.com/JavierSanchezCastro -- login: jgould22 - count: 17 - avatarUrl: https://avatars.githubusercontent.com/u/4335847?u=ed77f67e0bb069084639b24d812dbb2a2b1dc554&v=4 - url: https://github.com/jgould22 -- login: tiangolo - count: 14 - avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 - url: https://github.com/tiangolo -- 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 - 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: estebanx64 - count: 7 - avatarUrl: https://avatars.githubusercontent.com/u/10840422?u=1900887aeed268699e5ea6f3fb7db614f7b77cd3&v=4 - url: https://github.com/estebanx64 -- 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 - 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: 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 - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/12184311?u=9b29d5d1d71f5f1a7ef9e439963ad3529e3b33a4&v=4 - url: https://github.com/WilliamDEdwards - login: mattmess1221 - count: 3 + count: 15 avatarUrl: https://avatars.githubusercontent.com/u/3409962?u=d22ea18aa8ea688af25a45df306134d593621a44&v=4 url: https://github.com/mattmess1221 -- login: Jelle-tenB +last_month_experts: +- login: YuriiMotov + count: 31 + avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4 + url: https://github.com/YuriiMotov +- login: Toygarmetu + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/92878791?u=538530cb6d5554e71f9c28709d794db9a74d23d9&v=4 + url: https://github.com/Toygarmetu +- login: JavierSanchezCastro + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 + url: https://github.com/JavierSanchezCastro +- login: tiangolo count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/210023470?u=c25d66addf36a747bd9fab773c4a6e7b238f45d4&v=4 - url: https://github.com/Jelle-tenB -- login: viniciusCalcantara - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/108818737?u=80f3ec7427fa6a41d5896984d0c526432f2299fa&v=4 - url: https://github.com/viniciusCalcantara -- login: davidhuser - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/4357648?u=6ed702f8f6d49a8b2a0ed33cbd8ab59c2d7db7f7&v=4 - url: https://github.com/davidhuser -- login: dbfreem - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/9778569?u=f2f1e9135b5e4f1b0c6821a548b17f97572720fc&v=4 - url: https://github.com/dbfreem -- login: SobikXexe - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/87701130?v=4 - url: https://github.com/SobikXexe -- login: pawelad - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/7062874?u=d27dc220545a8401ad21840590a97d474d7101e6&v=4 - url: https://github.com/pawelad -- login: Isuxiz - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/48672727?u=34d7b4ade252687d22a27cf53037b735b244bfc1&v=4 - url: https://github.com/Isuxiz -- login: Minibrams - count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/8108085?u=b028dbc308fa8485e0e2e9402b3d03d8deb22bf9&v=4 - url: https://github.com/Minibrams -- login: adsouza + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 + url: https://github.com/tiangolo +- login: valentinDruzhinin count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/275832?u=f90f110cfafeafed2f14339e840941c2c328c186&v=4 - url: https://github.com/adsouza -- login: Synrom + avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 + url: https://github.com/valentinDruzhinin +three_months_experts: +- login: YuriiMotov + count: 91 + avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4 + url: https://github.com/YuriiMotov +- login: tiangolo + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 + url: https://github.com/tiangolo +- 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 + 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 + url: https://github.com/sachinh35 +- login: RichieB2B + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/1461970?u=edaa57d1077705244ea5c9244f4783d94ff11f12&v=4 + url: https://github.com/RichieB2B +- login: EmmanuelNiyonshuti count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/30272537?v=4 - url: https://github.com/Synrom -- login: gaby + avatarUrl: https://avatars.githubusercontent.com/u/142030687?u=ab131d5ad4670280a978f489babe71c9bf9c1097&v=4 + url: https://github.com/EmmanuelNiyonshuti +- login: luzzodev count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/835733?u=8c72dec16fa560bdc81113354f2ffd79ad062bde&v=4 - url: https://github.com/gaby -- login: Ale-Cas + avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4 + url: https://github.com/luzzodev +- login: davidbrochart count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/64859146?u=d52a6ecf8d83d2927e2ae270bdfcc83495dba8c9&v=4 - url: https://github.com/Ale-Cas -- login: CharlesPerrotMinotHCHB + avatarUrl: https://avatars.githubusercontent.com/u/4711805?u=d39696d995a9e02ec3613ffb2f62b20b14f92f26&v=4 + url: https://github.com/davidbrochart +- login: CharlieReitzel count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/112571330?u=e3a666718ff5ad1d1c49d6c31358a9f80c841b30&v=4 - url: https://github.com/CharlesPerrotMinotHCHB -- login: yanggeorge + avatarUrl: https://avatars.githubusercontent.com/u/20848272?v=4 + url: https://github.com/CharlieReitzel +- login: dotmitsu count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/2434407?v=4 - url: https://github.com/yanggeorge -- login: Brikas - count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/80290187?v=4 - url: https://github.com/Brikas + avatarUrl: https://avatars.githubusercontent.com/u/42657211?u=3bccc9a2f386a3f24230ec393080f8904fe2a5b2&v=4 + url: https://github.com/dotmitsu - login: dolfinus count: 2 avatarUrl: https://avatars.githubusercontent.com/u/4661021?u=ed5ddadcf36d9b943ebe61febe0b96ee34e5425d&v=4 url: https://github.com/dolfinus -- login: slafs +- login: garg-khushi count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/210173?v=4 - url: https://github.com/slafs -- login: purepani + avatarUrl: https://avatars.githubusercontent.com/u/139839680?u=7faffa70275f8ab16f163e0c742a11d2662f9c66&v=4 + url: https://github.com/garg-khushi +- login: florentx count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/7587353?v=4 - url: https://github.com/purepani -- login: ddahan + avatarUrl: https://avatars.githubusercontent.com/u/142113?u=bf10f10080026346b092633c380977b61cee0d9c&v=4 + url: https://github.com/florentx +six_months_experts: +- login: YuriiMotov + count: 163 + avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4 + url: https://github.com/YuriiMotov +- login: tiangolo + count: 24 + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 + 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 + count: 13 + avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 + url: https://github.com/JavierSanchezCastro +- login: Toygarmetu + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/92878791?u=538530cb6d5554e71f9c28709d794db9a74d23d9&v=4 + url: https://github.com/Toygarmetu +- login: valentinDruzhinin + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 + url: https://github.com/valentinDruzhinin +- 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: ArmanShirzad + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/68951175?u=1f1efae2fa5d0d17c38a1a8413bedca5e538cedb&v=4 + url: https://github.com/ArmanShirzad +- login: krylosov-aa + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/242901957?u=4c9c7b468203b09bca64936fb464620e32cdd252&v=4 + url: https://github.com/krylosov-aa +- login: sachinh35 + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/21972708?u=8560b97b8b41e175f476270b56de8a493b84f302&v=4 + url: https://github.com/sachinh35 +- 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 - avatarUrl: https://avatars.githubusercontent.com/u/1933516?u=1d200a620e8d6841df017e9f2bb7efb58b580f40&v=4 - url: https://github.com/ddahan -- login: TaigoFr + avatarUrl: https://avatars.githubusercontent.com/u/142030687?u=ab131d5ad4670280a978f489babe71c9bf9c1097&v=4 + url: https://github.com/EmmanuelNiyonshuti +- login: huynguyengl99 count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/17792131?u=372b27056ec82f1ae03d8b3f37ef55b04a7cfdd1&v=4 - url: https://github.com/TaigoFr -- login: Garrett-R + avatarUrl: https://avatars.githubusercontent.com/u/49433085?u=7b626115686c5d97a2a32a03119f5300e425cc9f&v=4 + url: https://github.com/huynguyengl99 +- login: davidbrochart count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/6614695?u=c128fd775002882f6e391bda5a89d1bdc5bdf45f&v=4 - url: https://github.com/Garrett-R -- login: jd-solanki + avatarUrl: https://avatars.githubusercontent.com/u/4711805?u=d39696d995a9e02ec3613ffb2f62b20b14f92f26&v=4 + url: https://github.com/davidbrochart +- login: CharlieReitzel count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/47495003?u=6e225cb42c688d0cd70e65c6baedb9f5922b1178&v=4 - url: https://github.com/jd-solanki -- login: EverStarck + avatarUrl: https://avatars.githubusercontent.com/u/20848272?v=4 + url: https://github.com/CharlieReitzel +- login: dotmitsu count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/51029456?u=343409b7cb6b3ea6a59359f4e8370d9c3f140ecd&v=4 - url: https://github.com/EverStarck -- login: henrymcl + avatarUrl: https://avatars.githubusercontent.com/u/42657211?u=3bccc9a2f386a3f24230ec393080f8904fe2a5b2&v=4 + url: https://github.com/dotmitsu +- login: dolfinus count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/26480299?v=4 - url: https://github.com/henrymcl + avatarUrl: https://avatars.githubusercontent.com/u/4661021?u=ed5ddadcf36d9b943ebe61febe0b96ee34e5425d&v=4 + url: https://github.com/dolfinus +- login: Kludex + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/7353520?u=df8a3f06ba8f55ae1967a3e2d5ed882903a4e330&v=4 + url: https://github.com/Kludex +- login: garg-khushi + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/139839680?u=7faffa70275f8ab16f163e0c742a11d2662f9c66&v=4 + url: https://github.com/garg-khushi +- login: skion + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/532192?v=4 + url: https://github.com/skion +- login: florentx + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/142113?u=bf10f10080026346b092633c380977b61cee0d9c&v=4 + url: https://github.com/florentx +- login: jc-louis + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/51329768?v=4 + url: https://github.com/jc-louis +- login: WilliamDEdwards + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/12184311?u=9b29d5d1d71f5f1a7ef9e439963ad3529e3b33a4&v=4 + url: https://github.com/WilliamDEdwards +- login: bughuntr7 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/236391583?u=7f51ff690e3a5711f845a115903c39e21c8af938&v=4 + url: https://github.com/bughuntr7 - login: jymchng count: 2 avatarUrl: https://avatars.githubusercontent.com/u/27895426?u=fb88c47775147d62a395fdb895d1af4148c7b566&v=4 url: https://github.com/jymchng -- login: christiansicari +- login: XieJiSS count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/29756552?v=4 - url: https://github.com/christiansicari -- login: JacobHayes + avatarUrl: https://avatars.githubusercontent.com/u/24671280?u=7ea0d9bfe46cf762594d62fd2f3c6d3813c3584c&v=4 + url: https://github.com/XieJiSS +- login: profatsky count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/2555532?u=354a525847a276bbb4426b0c95791a8ba5970f9b&v=4 - url: https://github.com/JacobHayes -- login: iloveitaly + avatarUrl: https://avatars.githubusercontent.com/u/92920843?u=81e54bb0b613c171f7cd0ab3cbb58873782c9c9c&v=4 + url: https://github.com/profatsky +one_year_experts: +- login: YuriiMotov + count: 918 + avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4 + url: https://github.com/YuriiMotov +- login: luzzodev + count: 60 + avatarUrl: https://avatars.githubusercontent.com/u/27291415?u=5607ae1ce75c5f54f09500ca854227f7bfd2033b&v=4 + url: https://github.com/luzzodev +- login: tiangolo + count: 31 + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 + url: https://github.com/tiangolo +- login: valentinDruzhinin + count: 30 + avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 + url: https://github.com/valentinDruzhinin +- login: JavierSanchezCastro + count: 19 + avatarUrl: https://avatars.githubusercontent.com/u/72013291?u=ae5679e6bd971d9d98cd5e76e8683f83642ba950&v=4 + url: https://github.com/JavierSanchezCastro +- login: alv2017 + count: 17 + avatarUrl: https://avatars.githubusercontent.com/u/31544722?v=4 + url: https://github.com/alv2017 +- login: engripaye + count: 14 + avatarUrl: https://avatars.githubusercontent.com/u/155247530?u=645169bc81856b7f1bd20090ecb0171a56dcbeb4&v=4 + url: https://github.com/engripaye +- login: sachinh35 + count: 12 + 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: Toygarmetu + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/92878791?u=538530cb6d5554e71f9c28709d794db9a74d23d9&v=4 + url: https://github.com/Toygarmetu +- login: raceychan + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/75417963?u=060c62870ec5a791765e63ac20d8885d11143786&v=4 + url: https://github.com/raceychan +- login: yinziyan1206 + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/37829370?u=da44ca53aefd5c23f346fab8e9fd2e108294c179&v=4 + 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 + count: 5 + avatarUrl: https://avatars.githubusercontent.com/u/14076775?u=ec43fe79a98dbc864b428afc7220753e25ca3af2&v=4 + url: https://github.com/DoctorJohn +- login: WilliamDEdwards + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/12184311?u=9b29d5d1d71f5f1a7ef9e439963ad3529e3b33a4&v=4 + url: https://github.com/WilliamDEdwards +- login: ArmanShirzad + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/68951175?u=1f1efae2fa5d0d17c38a1a8413bedca5e538cedb&v=4 + url: https://github.com/ArmanShirzad +- login: krylosov-aa + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/242901957?u=4c9c7b468203b09bca64936fb464620e32cdd252&v=4 + url: https://github.com/krylosov-aa +- login: isgin01 + count: 4 + avatarUrl: https://avatars.githubusercontent.com/u/157279130?u=16d6466476cf7dbc55a4cd575b6ea920ebdd81e1&v=4 + url: https://github.com/isgin01 +- login: sinisaos + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/30960668?v=4 + url: https://github.com/sinisaos +- login: dolfinus + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/4661021?u=ed5ddadcf36d9b943ebe61febe0b96ee34e5425d&v=4 + url: https://github.com/dolfinus +- login: jymchng + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/27895426?u=fb88c47775147d62a395fdb895d1af4148c7b566&v=4 + url: https://github.com/jymchng +- 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: Jelle-tenB + count: 3 + avatarUrl: https://avatars.githubusercontent.com/u/210023470?u=c25d66addf36a747bd9fab773c4a6e7b238f45d4&v=4 + url: https://github.com/Jelle-tenB +- login: Garrett-R count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/150855?v=4 - url: https://github.com/iloveitaly -- login: iiotsrc + avatarUrl: https://avatars.githubusercontent.com/u/6614695?u=c128fd775002882f6e391bda5a89d1bdc5bdf45f&v=4 + url: https://github.com/Garrett-R +- login: TaigoFr count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/131771119?u=bcaf2559ef6266af70b151b7fda31a1ee3dbecb3&v=4 - url: https://github.com/iiotsrc -- login: PidgeyBE + avatarUrl: https://avatars.githubusercontent.com/u/17792131?u=372b27056ec82f1ae03d8b3f37ef55b04a7cfdd1&v=4 + url: https://github.com/TaigoFr +- login: EmmanuelNiyonshuti count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/19860056?u=47b584eb1c1ab45e31c1b474109a962d7e82be49&v=4 - url: https://github.com/PidgeyBE -- login: KianAnbarestani + avatarUrl: https://avatars.githubusercontent.com/u/142030687?u=ab131d5ad4670280a978f489babe71c9bf9c1097&v=4 + url: https://github.com/EmmanuelNiyonshuti +- login: stan-dot count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/145364424?u=dcc3d8fb4ca07d36fb52a17f38b6650565de40be&v=4 - url: https://github.com/KianAnbarestani -- login: ykaiqx + avatarUrl: https://avatars.githubusercontent.com/u/56644812?u=a7dd773084f1c17c5f05019cc25a984e24873691&v=4 + url: https://github.com/stan-dot +- login: Damon0603 count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/56395004?u=1eebf5ce25a8067f7bfa6251a24f667be492d9d6&v=4 - url: https://github.com/ykaiqx -- login: AliYmn + avatarUrl: https://avatars.githubusercontent.com/u/110039208?u=f24bf5c30317bc4959118d1b919587c473a865b6&v=4 + url: https://github.com/Damon0603 +- login: huynguyengl99 count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/18416653?u=a77e2605e3ce6aaf6fef8ad4a7b0d32954fba47a&v=4 - url: https://github.com/AliYmn -- login: gelezo43 + avatarUrl: https://avatars.githubusercontent.com/u/49433085?u=7b626115686c5d97a2a32a03119f5300e425cc9f&v=4 + url: https://github.com/huynguyengl99 +- login: Ale-Cas count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/40732698?u=611f39d3c1d2f4207a590937a78c1f10eed6232c&v=4 - url: https://github.com/gelezo43 -- login: jfeaver + avatarUrl: https://avatars.githubusercontent.com/u/64859146?u=d52a6ecf8d83d2927e2ae270bdfcc83495dba8c9&v=4 + url: https://github.com/Ale-Cas +- login: tiborrr count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/1091338?u=0bcba366447d8fadad63f6705a52d128da4c7ec2&v=4 - url: https://github.com/jfeaver + avatarUrl: https://avatars.githubusercontent.com/u/16014746?u=0ce47015e53009e90393582fe86b7b90e809bc28&v=4 + url: https://github.com/tiborrr +- login: davidbrochart + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/4711805?u=d39696d995a9e02ec3613ffb2f62b20b14f92f26&v=4 + url: https://github.com/davidbrochart +- login: CharlieReitzel + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/20848272?v=4 + url: https://github.com/CharlieReitzel +- login: kiranzo + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/1070878?u=68f78a891c9751dd87571ac712a6309090c4bc01&v=4 + url: https://github.com/kiranzo +- login: dotmitsu + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/42657211?u=3bccc9a2f386a3f24230ec393080f8904fe2a5b2&v=4 + url: https://github.com/dotmitsu +- login: Brikas + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/80290187?u=2b72e497ca4444ecec1f9dc2d1b8d5437a27b83f&v=4 + url: https://github.com/Brikas +- login: BloodyRain2k + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/1014362?v=4 + url: https://github.com/BloodyRain2k +- login: usiqwerty + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/37992525?u=0c6e91d7b3887aa558755f4225ce74a003cbe852&v=4 + url: https://github.com/usiqwerty +- login: garg-khushi + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/139839680?u=7faffa70275f8ab16f163e0c742a11d2662f9c66&v=4 + url: https://github.com/garg-khushi +- login: sk- + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/911768?u=3bfaf87089eb03ef0fa378f316b9c783f431aa9b&v=4 + url: https://github.com/sk- +- login: skion + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/532192?v=4 + url: https://github.com/skion +- login: Danstiv + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/50794055?v=4 + url: https://github.com/Danstiv +- login: florentx + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/142113?u=bf10f10080026346b092633c380977b61cee0d9c&v=4 + url: https://github.com/florentx +- login: jc-louis + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/51329768?v=4 + url: https://github.com/jc-louis +- login: bughuntr7 + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/236391583?u=7f51ff690e3a5711f845a115903c39e21c8af938&v=4 + url: https://github.com/bughuntr7 +- login: purepani + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/7587353?v=4 + url: https://github.com/purepani +- login: asmaier + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/3169297?v=4 + url: https://github.com/asmaier +- login: henrymcl + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/26480299?v=4 + url: https://github.com/henrymcl +- login: potiuk + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/595491?v=4 + url: https://github.com/potiuk +- login: EverStarck + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/51029456?u=343409b7cb6b3ea6a59359f4e8370d9c3f140ecd&v=4 + url: https://github.com/EverStarck +- login: sanderbollen-clockworks + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/183479560?v=4 + url: https://github.com/sanderbollen-clockworks +- login: davidhuser + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/4357648?u=6ed702f8f6d49a8b2a0ed33cbd8ab59c2d7db7f7&v=4 + url: https://github.com/davidhuser +- login: XieJiSS + count: 2 + avatarUrl: https://avatars.githubusercontent.com/u/24671280?u=7ea0d9bfe46cf762594d62fd2f3c6d3813c3584c&v=4 + url: https://github.com/XieJiSS diff --git a/docs/en/data/topic_repos.yml b/docs/en/data/topic_repos.yml index a37cb6dcfd..693d08ffd5 100644 --- a/docs/en/data/topic_repos.yml +++ b/docs/en/data/topic_repos.yml @@ -1,191 +1,191 @@ - name: full-stack-fastapi-template html_url: https://github.com/fastapi/full-stack-fastapi-template - stars: 41312 + stars: 41789 owner_login: fastapi owner_html_url: https://github.com/fastapi - name: Hello-Python html_url: https://github.com/mouredev/Hello-Python - stars: 34206 + stars: 34587 owner_login: mouredev owner_html_url: https://github.com/mouredev - name: serve html_url: https://github.com/jina-ai/serve - stars: 21832 + stars: 21835 owner_login: jina-ai owner_html_url: https://github.com/jina-ai - name: HivisionIDPhotos html_url: https://github.com/Zeyi-Lin/HivisionIDPhotos - stars: 20661 + stars: 20755 owner_login: Zeyi-Lin owner_html_url: https://github.com/Zeyi-Lin - name: sqlmodel html_url: https://github.com/fastapi/sqlmodel - stars: 17567 + stars: 17687 owner_login: fastapi owner_html_url: https://github.com/fastapi - name: fastapi-best-practices html_url: https://github.com/zhanymkanov/fastapi-best-practices - stars: 16291 + stars: 16611 owner_login: zhanymkanov owner_html_url: https://github.com/zhanymkanov - name: Douyin_TikTok_Download_API html_url: https://github.com/Evil0ctal/Douyin_TikTok_Download_API - stars: 16132 + stars: 16474 owner_login: Evil0ctal owner_html_url: https://github.com/Evil0ctal - name: SurfSense html_url: https://github.com/MODSetter/SurfSense - stars: 12723 + stars: 13069 owner_login: MODSetter owner_html_url: https://github.com/MODSetter - name: machine-learning-zoomcamp html_url: https://github.com/DataTalksClub/machine-learning-zoomcamp - stars: 12575 + stars: 12674 owner_login: DataTalksClub owner_html_url: https://github.com/DataTalksClub - name: fastapi_mcp html_url: https://github.com/tadata-org/fastapi_mcp - stars: 11478 + stars: 11604 owner_login: tadata-org owner_html_url: https://github.com/tadata-org - name: awesome-fastapi html_url: https://github.com/mjhea0/awesome-fastapi - stars: 11018 + stars: 11119 owner_login: mjhea0 owner_html_url: https://github.com/mjhea0 - name: XHS-Downloader html_url: https://github.com/JoeanAmier/XHS-Downloader - stars: 9938 + stars: 10206 owner_login: JoeanAmier owner_html_url: https://github.com/JoeanAmier - name: polar html_url: https://github.com/polarsource/polar - stars: 9348 + stars: 9500 owner_login: polarsource owner_html_url: https://github.com/polarsource - name: FastUI html_url: https://github.com/pydantic/FastUI - stars: 8949 + stars: 8956 owner_login: pydantic owner_html_url: https://github.com/pydantic - name: FileCodeBox html_url: https://github.com/vastsa/FileCodeBox - stars: 8060 + stars: 8128 owner_login: vastsa owner_html_url: https://github.com/vastsa - name: nonebot2 html_url: https://github.com/nonebot/nonebot2 - stars: 7311 + stars: 7384 owner_login: nonebot owner_html_url: https://github.com/nonebot - name: hatchet html_url: https://github.com/hatchet-dev/hatchet - stars: 6479 + stars: 6659 owner_login: hatchet-dev owner_html_url: https://github.com/hatchet-dev - name: fastapi-users html_url: https://github.com/fastapi-users/fastapi-users - stars: 5970 + stars: 6024 owner_login: fastapi-users owner_html_url: https://github.com/fastapi-users - name: serge html_url: https://github.com/serge-chat/serge - stars: 5751 + stars: 5746 owner_login: serge-chat owner_html_url: https://github.com/serge-chat - name: strawberry html_url: https://github.com/strawberry-graphql/strawberry - stars: 4598 + stars: 4616 owner_login: strawberry-graphql owner_html_url: https://github.com/strawberry-graphql - name: devpush html_url: https://github.com/hunvreus/devpush - stars: 4407 + stars: 4515 owner_login: hunvreus owner_html_url: https://github.com/hunvreus - name: Kokoro-FastAPI html_url: https://github.com/remsky/Kokoro-FastAPI - stars: 4359 + stars: 4494 owner_login: remsky owner_html_url: https://github.com/remsky +- name: Yuxi-Know + html_url: https://github.com/xerrors/Yuxi-Know + stars: 4404 + owner_login: xerrors + owner_html_url: https://github.com/xerrors - name: poem html_url: https://github.com/poem-web/poem - stars: 4337 + stars: 4359 owner_login: poem-web owner_html_url: https://github.com/poem-web - name: chatgpt-web-share html_url: https://github.com/chatpire/chatgpt-web-share - stars: 4279 + stars: 4274 owner_login: chatpire owner_html_url: https://github.com/chatpire - name: dynaconf html_url: https://github.com/dynaconf/dynaconf - stars: 4244 + stars: 4266 owner_login: dynaconf owner_html_url: https://github.com/dynaconf -- name: Yuxi-Know - html_url: https://github.com/xerrors/Yuxi-Know - stars: 4154 - owner_login: xerrors - owner_html_url: https://github.com/xerrors - name: atrilabs-engine html_url: https://github.com/Atri-Labs/atrilabs-engine - stars: 4086 + stars: 4085 owner_login: Atri-Labs owner_html_url: https://github.com/Atri-Labs - name: logfire html_url: https://github.com/pydantic/logfire - stars: 3975 + stars: 4050 owner_login: pydantic owner_html_url: https://github.com/pydantic -- name: LitServe - html_url: https://github.com/Lightning-AI/LitServe - stars: 3797 - owner_login: Lightning-AI - owner_html_url: https://github.com/Lightning-AI - name: huma html_url: https://github.com/danielgtaylor/huma - stars: 3785 + stars: 3848 owner_login: danielgtaylor owner_html_url: https://github.com/danielgtaylor +- name: LitServe + html_url: https://github.com/Lightning-AI/LitServe + stars: 3803 + owner_login: Lightning-AI + owner_html_url: https://github.com/Lightning-AI - name: datamodel-code-generator html_url: https://github.com/koxudaxi/datamodel-code-generator - stars: 3731 + stars: 3785 owner_login: koxudaxi owner_html_url: https://github.com/koxudaxi - name: fastapi-admin html_url: https://github.com/fastapi-admin/fastapi-admin - stars: 3697 + stars: 3717 owner_login: fastapi-admin owner_html_url: https://github.com/fastapi-admin - name: farfalle html_url: https://github.com/rashadphz/farfalle - stars: 3506 + stars: 3515 owner_login: rashadphz owner_html_url: https://github.com/rashadphz - name: tracecat html_url: https://github.com/TracecatHQ/tracecat - stars: 3458 + stars: 3498 owner_login: TracecatHQ owner_html_url: https://github.com/TracecatHQ - name: mcp-context-forge html_url: https://github.com/IBM/mcp-context-forge - stars: 3216 + stars: 3347 owner_login: IBM owner_html_url: https://github.com/IBM - name: opyrator html_url: https://github.com/ml-tooling/opyrator - stars: 3134 + stars: 3139 owner_login: ml-tooling owner_html_url: https://github.com/ml-tooling - name: docarray html_url: https://github.com/docarray/docarray - stars: 3111 + stars: 3116 owner_login: docarray owner_html_url: https://github.com/docarray - name: fastapi-realworld-example-app html_url: https://github.com/nsidnev/fastapi-realworld-example-app - stars: 3072 + stars: 3079 owner_login: nsidnev owner_html_url: https://github.com/nsidnev - name: uvicorn-gunicorn-fastapi-docker @@ -195,64 +195,64 @@ owner_html_url: https://github.com/tiangolo - name: FastAPI-template html_url: https://github.com/s3rius/FastAPI-template - stars: 2728 + stars: 2749 owner_login: s3rius owner_html_url: https://github.com/s3rius - name: best-of-web-python html_url: https://github.com/ml-tooling/best-of-web-python - stars: 2686 + stars: 2695 owner_login: ml-tooling owner_html_url: https://github.com/ml-tooling -- name: YC-Killer - html_url: https://github.com/sahibzada-allahyar/YC-Killer - stars: 2648 - owner_login: sahibzada-allahyar - owner_html_url: https://github.com/sahibzada-allahyar - name: sqladmin html_url: https://github.com/aminalaee/sqladmin - stars: 2637 + stars: 2674 owner_login: aminalaee owner_html_url: https://github.com/aminalaee +- name: YC-Killer + html_url: https://github.com/sahibzada-allahyar/YC-Killer + stars: 2665 + owner_login: sahibzada-allahyar + owner_html_url: https://github.com/sahibzada-allahyar - name: fastapi-react html_url: https://github.com/Buuntu/fastapi-react - stars: 2573 + stars: 2585 owner_login: Buuntu owner_html_url: https://github.com/Buuntu - name: RasaGPT html_url: https://github.com/paulpierre/RasaGPT - stars: 2460 + stars: 2462 owner_login: paulpierre owner_html_url: https://github.com/paulpierre - name: supabase-py html_url: https://github.com/supabase/supabase-py - stars: 2428 + stars: 2452 owner_login: supabase owner_html_url: https://github.com/supabase - name: 30-Days-of-Python html_url: https://github.com/codingforentrepreneurs/30-Days-of-Python - stars: 2347 + stars: 2435 owner_login: codingforentrepreneurs owner_html_url: https://github.com/codingforentrepreneurs +- name: NoteDiscovery + html_url: https://github.com/gamosoft/NoteDiscovery + stars: 2354 + owner_login: gamosoft + owner_html_url: https://github.com/gamosoft - name: nextpy html_url: https://github.com/dot-agent/nextpy - stars: 2337 + stars: 2335 owner_login: dot-agent owner_html_url: https://github.com/dot-agent - name: fastapi-utils html_url: https://github.com/fastapiutils/fastapi-utils - stars: 2299 + stars: 2306 owner_login: fastapiutils owner_html_url: https://github.com/fastapiutils - name: langserve html_url: https://github.com/langchain-ai/langserve - stars: 2255 + stars: 2276 owner_login: langchain-ai owner_html_url: https://github.com/langchain-ai -- name: NoteDiscovery - html_url: https://github.com/gamosoft/NoteDiscovery - stars: 2182 - owner_login: gamosoft - owner_html_url: https://github.com/gamosoft - name: solara html_url: https://github.com/widgetti/solara stars: 2154 @@ -260,236 +260,236 @@ owner_html_url: https://github.com/widgetti - name: mangum html_url: https://github.com/Kludex/mangum - stars: 2071 + stars: 2084 owner_login: Kludex owner_html_url: https://github.com/Kludex - name: fastapi_best_architecture html_url: https://github.com/fastapi-practices/fastapi_best_architecture - stars: 2036 + stars: 2083 owner_login: fastapi-practices owner_html_url: https://github.com/fastapi-practices - name: vue-fastapi-admin html_url: https://github.com/mizhexiaoxiao/vue-fastapi-admin - stars: 1983 + stars: 2012 owner_login: mizhexiaoxiao owner_html_url: https://github.com/mizhexiaoxiao -- name: agentkit - html_url: https://github.com/BCG-X-Official/agentkit - stars: 1941 - owner_login: BCG-X-Official - owner_html_url: https://github.com/BCG-X-Official - name: fastapi-langgraph-agent-production-ready-template html_url: https://github.com/wassim249/fastapi-langgraph-agent-production-ready-template - stars: 1920 + stars: 2006 owner_login: wassim249 owner_html_url: https://github.com/wassim249 +- name: agentkit + html_url: https://github.com/BCG-X-Official/agentkit + stars: 1946 + owner_login: BCG-X-Official + owner_html_url: https://github.com/BCG-X-Official +- name: slowapi + html_url: https://github.com/laurentS/slowapi + stars: 1924 + owner_login: laurentS + owner_html_url: https://github.com/laurentS - name: openapi-python-client html_url: https://github.com/openapi-generators/openapi-python-client - stars: 1900 + stars: 1915 owner_login: openapi-generators owner_html_url: https://github.com/openapi-generators - name: manage-fastapi html_url: https://github.com/ycd/manage-fastapi - stars: 1894 + stars: 1898 owner_login: ycd owner_html_url: https://github.com/ycd -- name: slowapi - html_url: https://github.com/laurentS/slowapi - stars: 1891 - owner_login: laurentS - owner_html_url: https://github.com/laurentS - name: piccolo html_url: https://github.com/piccolo-orm/piccolo - stars: 1854 + stars: 1864 owner_login: piccolo-orm owner_html_url: https://github.com/piccolo-orm - name: fastapi-cache html_url: https://github.com/long2ice/fastapi-cache - stars: 1816 + stars: 1837 owner_login: long2ice owner_html_url: https://github.com/long2ice +- name: FastAPI-boilerplate + html_url: https://github.com/benavlabs/FastAPI-boilerplate + stars: 1820 + owner_login: benavlabs + owner_html_url: https://github.com/benavlabs - name: python-week-2022 html_url: https://github.com/rochacbruno/python-week-2022 - stars: 1813 + stars: 1811 owner_login: rochacbruno owner_html_url: https://github.com/rochacbruno - name: ormar - html_url: https://github.com/collerek/ormar - stars: 1797 - owner_login: collerek - owner_html_url: https://github.com/collerek -- name: FastAPI-boilerplate - html_url: https://github.com/benavlabs/FastAPI-boilerplate - stars: 1792 - owner_login: benavlabs - owner_html_url: https://github.com/benavlabs + html_url: https://github.com/ormar-orm/ormar + stars: 1801 + owner_login: ormar-orm + owner_html_url: https://github.com/ormar-orm - name: termpair html_url: https://github.com/cs01/termpair - stars: 1727 + stars: 1728 owner_login: cs01 owner_html_url: https://github.com/cs01 - name: fastapi-crudrouter html_url: https://github.com/awtkns/fastapi-crudrouter - stars: 1677 + stars: 1682 owner_login: awtkns owner_html_url: https://github.com/awtkns - name: langchain-serve html_url: https://github.com/jina-ai/langchain-serve - stars: 1634 + stars: 1633 owner_login: jina-ai owner_html_url: https://github.com/jina-ai - name: fastapi-pagination html_url: https://github.com/uriyyo/fastapi-pagination - stars: 1607 + stars: 1631 owner_login: uriyyo owner_html_url: https://github.com/uriyyo -- name: awesome-fastapi-projects - html_url: https://github.com/Kludex/awesome-fastapi-projects - stars: 1592 - owner_login: Kludex - owner_html_url: https://github.com/Kludex - name: bracket html_url: https://github.com/evroon/bracket - stars: 1580 + stars: 1619 owner_login: evroon owner_html_url: https://github.com/evroon +- name: awesome-fastapi-projects + html_url: https://github.com/Kludex/awesome-fastapi-projects + stars: 1596 + owner_login: Kludex + owner_html_url: https://github.com/Kludex - name: coronavirus-tracker-api html_url: https://github.com/ExpDev07/coronavirus-tracker-api - stars: 1570 + stars: 1568 owner_login: ExpDev07 owner_html_url: https://github.com/ExpDev07 - name: fastapi-amis-admin html_url: https://github.com/amisadmin/fastapi-amis-admin - stars: 1512 + stars: 1520 owner_login: amisadmin owner_html_url: https://github.com/amisadmin - name: fastcrud html_url: https://github.com/benavlabs/fastcrud - stars: 1471 + stars: 1487 owner_login: benavlabs owner_html_url: https://github.com/benavlabs - name: fastapi-boilerplate html_url: https://github.com/teamhide/fastapi-boilerplate - stars: 1461 + stars: 1465 owner_login: teamhide owner_html_url: https://github.com/teamhide - name: awesome-python-resources html_url: https://github.com/DjangoEx/awesome-python-resources - stars: 1435 + stars: 1441 owner_login: DjangoEx owner_html_url: https://github.com/DjangoEx - name: prometheus-fastapi-instrumentator html_url: https://github.com/trallnag/prometheus-fastapi-instrumentator - stars: 1417 + stars: 1433 owner_login: trallnag owner_html_url: https://github.com/trallnag - name: fastapi-code-generator html_url: https://github.com/koxudaxi/fastapi-code-generator - stars: 1382 + stars: 1384 owner_login: koxudaxi owner_html_url: https://github.com/koxudaxi -- name: fastapi-scaff - html_url: https://github.com/atpuxiner/fastapi-scaff - stars: 1367 - owner_login: atpuxiner - owner_html_url: https://github.com/atpuxiner - name: fastapi-tutorial html_url: https://github.com/liaogx/fastapi-tutorial - stars: 1360 + stars: 1365 owner_login: liaogx owner_html_url: https://github.com/liaogx +- name: WebRPA + html_url: https://github.com/pmh1314520/WebRPA + stars: 1354 + owner_login: pmh1314520 + owner_html_url: https://github.com/pmh1314520 - name: budgetml html_url: https://github.com/ebhy/budgetml - stars: 1343 + stars: 1344 owner_login: ebhy owner_html_url: https://github.com/ebhy +- name: fastapi-scaff + html_url: https://github.com/atpuxiner/fastapi-scaff + stars: 1305 + owner_login: atpuxiner + owner_html_url: https://github.com/atpuxiner - name: bolt-python html_url: https://github.com/slackapi/bolt-python - stars: 1276 + stars: 1278 owner_login: slackapi owner_html_url: https://github.com/slackapi - name: bedrock-chat html_url: https://github.com/aws-samples/bedrock-chat - stars: 1268 + stars: 1271 owner_login: aws-samples owner_html_url: https://github.com/aws-samples - name: fastapi-alembic-sqlmodel-async html_url: https://github.com/vargasjona/fastapi-alembic-sqlmodel-async - stars: 1265 + stars: 1269 owner_login: vargasjona owner_html_url: https://github.com/vargasjona - name: fastapi_production_template html_url: https://github.com/zhanymkanov/fastapi_production_template - stars: 1227 + stars: 1231 owner_login: zhanymkanov owner_html_url: https://github.com/zhanymkanov - name: restish html_url: https://github.com/rest-sh/restish - stars: 1200 + stars: 1225 owner_login: rest-sh owner_html_url: https://github.com/rest-sh -- name: langchain-extract - html_url: https://github.com/langchain-ai/langchain-extract - stars: 1183 - owner_login: langchain-ai - owner_html_url: https://github.com/langchain-ai -- name: odmantic - html_url: https://github.com/art049/odmantic - stars: 1162 - owner_login: art049 - owner_html_url: https://github.com/art049 - name: aktools html_url: https://github.com/akfamily/aktools - stars: 1155 + stars: 1223 owner_login: akfamily owner_html_url: https://github.com/akfamily - name: RuoYi-Vue3-FastAPI html_url: https://github.com/insistence/RuoYi-Vue3-FastAPI - stars: 1155 + stars: 1202 owner_login: insistence owner_html_url: https://github.com/insistence +- name: langchain-extract + html_url: https://github.com/langchain-ai/langchain-extract + stars: 1189 + owner_login: langchain-ai + owner_html_url: https://github.com/langchain-ai +- name: odmantic + html_url: https://github.com/art049/odmantic + stars: 1167 + owner_login: art049 + owner_html_url: https://github.com/art049 - name: authx html_url: https://github.com/yezz123/authx - stars: 1142 + stars: 1144 owner_login: yezz123 owner_html_url: https://github.com/yezz123 -- name: SAG - html_url: https://github.com/Zleap-AI/SAG - stars: 1110 - owner_login: Zleap-AI - owner_html_url: https://github.com/Zleap-AI -- name: flock - html_url: https://github.com/Onelevenvy/flock - stars: 1069 - owner_login: Onelevenvy - owner_html_url: https://github.com/Onelevenvy -- name: fastapi-observability - html_url: https://github.com/blueswen/fastapi-observability - stars: 1063 - owner_login: blueswen - owner_html_url: https://github.com/blueswen - name: enterprise-deep-research html_url: https://github.com/SalesforceAIResearch/enterprise-deep-research - stars: 1061 + stars: 1123 owner_login: SalesforceAIResearch owner_html_url: https://github.com/SalesforceAIResearch -- name: titiler - html_url: https://github.com/developmentseed/titiler - stars: 1039 - owner_login: developmentseed - owner_html_url: https://github.com/developmentseed +- name: SAG + html_url: https://github.com/Zleap-AI/SAG + stars: 1115 + owner_login: Zleap-AI + owner_html_url: https://github.com/Zleap-AI +- name: FileSync + html_url: https://github.com/polius/FileSync + stars: 1111 + owner_login: polius + owner_html_url: https://github.com/polius - name: every-pdf html_url: https://github.com/DDULDDUCK/every-pdf - stars: 1017 + stars: 1093 owner_login: DDULDDUCK owner_html_url: https://github.com/DDULDDUCK -- name: autollm - html_url: https://github.com/viddexa/autollm - stars: 1005 - owner_login: viddexa - owner_html_url: https://github.com/viddexa -- name: lanarky - html_url: https://github.com/ajndkr/lanarky - stars: 995 - owner_login: ajndkr - owner_html_url: https://github.com/ajndkr +- name: fastapi-observability + html_url: https://github.com/blueswen/fastapi-observability + stars: 1079 + owner_login: blueswen + owner_html_url: https://github.com/blueswen +- name: flock + html_url: https://github.com/Onelevenvy/flock + stars: 1073 + owner_login: Onelevenvy + owner_html_url: https://github.com/Onelevenvy +- name: titiler + html_url: https://github.com/developmentseed/titiler + stars: 1060 + owner_login: developmentseed + owner_html_url: https://github.com/developmentseed diff --git a/docs/en/data/translation_reviewers.yml b/docs/en/data/translation_reviewers.yml index 3d321818fc..73f6dc6f0d 100644 --- a/docs/en/data/translation_reviewers.yml +++ b/docs/en/data/translation_reviewers.yml @@ -31,7 +31,7 @@ hard-coders: hasansezertasan: login: hasansezertasan count: 95 - avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=99f0b0f0fc47e88e8abb337b4447357939ef93e7&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=d36995e41a00590da64e6204cfd112e0484ac1ca&v=4 url: https://github.com/hasansezertasan alv2017: login: alv2017 @@ -43,21 +43,31 @@ nazarepiedady: count: 87 avatarUrl: https://avatars.githubusercontent.com/u/31008635?u=f69ddc4ea8bda3bdfac7aa0e2ea38de282e6ee2d&v=4 url: https://github.com/nazarepiedady +tiangolo: + login: tiangolo + count: 82 + avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 + url: https://github.com/tiangolo AlertRED: login: AlertRED count: 81 avatarUrl: https://avatars.githubusercontent.com/u/15695000?u=f5a4944c6df443030409c88da7d7fa0b7ead985c&v=4 url: https://github.com/AlertRED -tiangolo: - login: tiangolo - count: 78 - avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 - url: https://github.com/tiangolo Alexandrhub: login: Alexandrhub count: 68 avatarUrl: https://avatars.githubusercontent.com/u/119126536?u=9fc0d48f3307817bafecc5861eb2168401a6cb04&v=4 url: https://github.com/Alexandrhub +nilslindemann: + login: nilslindemann + count: 67 + avatarUrl: https://avatars.githubusercontent.com/u/6892179?u=1dca6a22195d6cd1ab20737c0e19a4c55d639472&v=4 + url: https://github.com/nilslindemann +YuriiMotov: + login: YuriiMotov + count: 65 + avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4 + url: https://github.com/YuriiMotov cassiobotaro: login: cassiobotaro count: 64 @@ -68,21 +78,11 @@ waynerv: count: 63 avatarUrl: https://avatars.githubusercontent.com/u/39515546?u=ec35139777597cdbbbddda29bf8b9d4396b429a9&v=4 url: https://github.com/waynerv -nilslindemann: - login: nilslindemann - count: 61 - avatarUrl: https://avatars.githubusercontent.com/u/6892179?u=1dca6a22195d6cd1ab20737c0e19a4c55d639472&v=4 - url: https://github.com/nilslindemann mattwang44: login: mattwang44 count: 61 avatarUrl: https://avatars.githubusercontent.com/u/24987826?u=58e37fb3927b9124b458945ac4c97aa0f1062d85&v=4 url: https://github.com/mattwang44 -YuriiMotov: - login: YuriiMotov - count: 56 - avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4 - url: https://github.com/YuriiMotov Laineyzhang55: login: Laineyzhang55 count: 48 @@ -128,6 +128,11 @@ solomein-sv: count: 38 avatarUrl: https://avatars.githubusercontent.com/u/46193920?u=789927ee09cfabd752d3bd554fa6baf4850d2777&v=4 url: https://github.com/solomein-sv +mezgoodle: + login: mezgoodle + count: 38 + avatarUrl: https://avatars.githubusercontent.com/u/41520940?u=4a9c765af688389d54296845d18b8f6cd6ddf09a&v=4 + url: https://github.com/mezgoodle JavierSanchezCastro: login: JavierSanchezCastro count: 38 @@ -138,11 +143,6 @@ alejsdev: count: 37 avatarUrl: https://avatars.githubusercontent.com/u/90076947?u=0facffe3abf87f57a1f05fa773d1119cc5c2f6a5&v=4 url: https://github.com/alejsdev -mezgoodle: - login: mezgoodle - count: 37 - avatarUrl: https://avatars.githubusercontent.com/u/41520940?u=4a9c765af688389d54296845d18b8f6cd6ddf09a&v=4 - url: https://github.com/mezgoodle stlucasgarcia: login: stlucasgarcia count: 36 @@ -163,21 +163,21 @@ rjNemo: count: 34 avatarUrl: https://avatars.githubusercontent.com/u/56785022?u=d5c3a02567c8649e146fcfc51b6060ccaf8adef8&v=4 url: https://github.com/rjNemo -codingjenny: - login: codingjenny +yychanlee: + login: yychanlee count: 34 avatarUrl: https://avatars.githubusercontent.com/u/103817302?u=3a042740dc0ff58615da0d8679230966fd7693e8&v=4 - url: https://github.com/codingjenny + url: https://github.com/yychanlee +Vincy1230: + login: Vincy1230 + count: 34 + avatarUrl: https://avatars.githubusercontent.com/u/81342412?u=ab5e256a4077a4a91f3f9cd2115ba80780454cbe&v=4 + url: https://github.com/Vincy1230 akarev0: login: akarev0 count: 33 avatarUrl: https://avatars.githubusercontent.com/u/53393089?u=6e528bb4789d56af887ce6fe237bea4010885406&v=4 url: https://github.com/akarev0 -Vincy1230: - login: Vincy1230 - count: 33 - avatarUrl: https://avatars.githubusercontent.com/u/81342412?u=ab5e256a4077a4a91f3f9cd2115ba80780454cbe&v=4 - url: https://github.com/Vincy1230 romashevchenko: login: romashevchenko count: 32 @@ -313,6 +313,11 @@ sattosan: count: 19 avatarUrl: https://avatars.githubusercontent.com/u/20574756?u=b0d8474d2938189c6954423ae8d81d91013f80a8&v=4 url: https://github.com/sattosan +maru0123-2004: + login: maru0123-2004 + count: 19 + avatarUrl: https://avatars.githubusercontent.com/u/43961566?u=16ed8603a4d6a4665cb6c53a7aece6f31379b769&v=4 + url: https://github.com/maru0123-2004 yes0ng: login: yes0ng count: 19 @@ -383,11 +388,6 @@ Joao-Pedro-P-Holanda: count: 16 avatarUrl: https://avatars.githubusercontent.com/u/110267046?u=331bd016326dac4cf3df4848f6db2dbbf8b5f978&v=4 url: https://github.com/Joao-Pedro-P-Holanda -maru0123-2004: - login: maru0123-2004 - count: 16 - avatarUrl: https://avatars.githubusercontent.com/u/43961566?u=16ed8603a4d6a4665cb6c53a7aece6f31379b769&v=4 - url: https://github.com/maru0123-2004 JaeHyuckSa: login: JaeHyuckSa count: 16 @@ -683,6 +683,11 @@ JoaoGustavoRogel: count: 9 avatarUrl: https://avatars.githubusercontent.com/u/29525510?u=a0a91251f5e43e132608d55d28ccb8645c5ea405&v=4 url: https://github.com/JoaoGustavoRogel +valentinDruzhinin: + login: valentinDruzhinin + count: 9 + avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 + url: https://github.com/valentinDruzhinin Yarous: login: Yarous count: 9 @@ -738,6 +743,11 @@ sungchan1: count: 8 avatarUrl: https://avatars.githubusercontent.com/u/28076127?u=fadbf24840186aca639d344bb3e0ecf7ff3441cf&v=4 url: https://github.com/sungchan1 +roli2py: + login: roli2py + count: 8 + avatarUrl: https://avatars.githubusercontent.com/u/61126128?u=bcb7a286e435a6b9d6a84b07db1232580ee796d4&v=4 + url: https://github.com/roli2py Serrones: login: Serrones count: 7 @@ -783,11 +793,6 @@ d2a-raudenaerde: count: 7 avatarUrl: https://avatars.githubusercontent.com/u/5213150?u=e6d0ef65c571c7e544fc1c7ec151c7c0a72fb6bb&v=4 url: https://github.com/d2a-raudenaerde -valentinDruzhinin: - login: valentinDruzhinin - count: 7 - avatarUrl: https://avatars.githubusercontent.com/u/12831905?u=aae1ebc675c91e8fa582df4fcc4fc4128106344d&v=4 - url: https://github.com/valentinDruzhinin Zerohertz: login: Zerohertz count: 7 @@ -1271,7 +1276,7 @@ rafsaf: frnsimoes: login: frnsimoes count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/66239468?u=cba345870d8d6b25dd6d56ee18f7120581e3c573&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/66239468?u=98fb2a38bcac765ea9651af8a0ab8f37df86570d&v=4 url: https://github.com/frnsimoes lieryan: login: lieryan @@ -1371,7 +1376,7 @@ nymous: EpsilonRationes: login: EpsilonRationes count: 3 - avatarUrl: https://avatars.githubusercontent.com/u/148639079?v=4 + avatarUrl: https://avatars.githubusercontent.com/u/148639079?u=5dd6c4a3f570dea44d208465fd10b709bcdfa69a&v=4 url: https://github.com/EpsilonRationes SametEmin: login: SametEmin @@ -1611,7 +1616,7 @@ raphaelauv: Fahad-Md-Kamal: login: Fahad-Md-Kamal count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/34704464?u=141086368c5557d5a1a533fe291f21f9fc584458&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/34704464?u=0b1da22a9b88b14d99e7e4368eadde7ecd695366&v=4 url: https://github.com/Fahad-Md-Kamal zxcq544: login: zxcq544 diff --git a/docs/en/data/translators.yml b/docs/en/data/translators.yml index dd5900a417..38a3306dc4 100644 --- a/docs/en/data/translators.yml +++ b/docs/en/data/translators.yml @@ -10,7 +10,7 @@ jaystone776: url: https://github.com/jaystone776 tiangolo: login: tiangolo - count: 31 + count: 46 avatarUrl: https://avatars.githubusercontent.com/u/1326112?u=cb5d06e73a9e1998141b1641aa88e443c6717651&v=4 url: https://github.com/tiangolo ceb10n: @@ -33,10 +33,15 @@ SwftAlpc: count: 23 avatarUrl: https://avatars.githubusercontent.com/u/52768429?u=6a3aa15277406520ad37f6236e89466ed44bc5b8&v=4 url: https://github.com/SwftAlpc +YuriiMotov: + login: YuriiMotov + count: 23 + avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4 + url: https://github.com/YuriiMotov hasansezertasan: login: hasansezertasan count: 22 - avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=99f0b0f0fc47e88e8abb337b4447357939ef93e7&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/13135006?u=d36995e41a00590da64e6204cfd112e0484ac1ca&v=4 url: https://github.com/hasansezertasan waynerv: login: waynerv @@ -58,11 +63,11 @@ Joao-Pedro-P-Holanda: count: 14 avatarUrl: https://avatars.githubusercontent.com/u/110267046?u=331bd016326dac4cf3df4848f6db2dbbf8b5f978&v=4 url: https://github.com/Joao-Pedro-P-Holanda -codingjenny: - login: codingjenny +yychanlee: + login: yychanlee count: 14 avatarUrl: https://avatars.githubusercontent.com/u/103817302?u=3a042740dc0ff58615da0d8679230966fd7693e8&v=4 - url: https://github.com/codingjenny + url: https://github.com/yychanlee Xewus: login: Xewus count: 13 @@ -108,11 +113,6 @@ pablocm83: count: 8 avatarUrl: https://avatars.githubusercontent.com/u/28315068?u=3310fbb05bb8bfc50d2c48b6cb64ac9ee4a14549&v=4 url: https://github.com/pablocm83 -YuriiMotov: - login: YuriiMotov - count: 8 - avatarUrl: https://avatars.githubusercontent.com/u/109919500?u=bc48be95c429989224786106b027f3c5e40cc354&v=4 - url: https://github.com/YuriiMotov ptt3199: login: ptt3199 count: 7 @@ -466,7 +466,7 @@ ArtemKhymenko: hasnatsajid: login: hasnatsajid count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/86589885?v=4 + avatarUrl: https://avatars.githubusercontent.com/u/86589885?u=3712c0362d7a4000d76022339c545cf46aa5903f&v=4 url: https://github.com/hasnatsajid alperiox: login: alperiox @@ -481,7 +481,7 @@ emrhnsyts: vusallyv: login: vusallyv count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/85983771?u=6fb8e2f876bca06e9f846606423c8f18fb46ad06&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/85983771?u=620ce103dcdc47953c952bb8d402a9cf8199014d&v=4 url: https://github.com/vusallyv jackleeio: login: jackleeio @@ -496,7 +496,7 @@ choi-haram: imtiaz101325: login: imtiaz101325 count: 2 - avatarUrl: https://avatars.githubusercontent.com/u/54007087?u=194d972b501b9ea9d2ddeaed757c492936e0121a&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/54007087?u=61e79c4c39798cd4d339788045dc44d4c6252bde&v=4 url: https://github.com/imtiaz101325 fabianfalon: login: fabianfalon diff --git a/docs/en/docs/_llm-test.md b/docs/en/docs/_llm-test.md index ff0e24f26c..cdca5857e6 100644 --- a/docs/en/docs/_llm-test.md +++ b/docs/en/docs/_llm-test.md @@ -166,7 +166,7 @@ See sections `### Special blocks` and `### Tab blocks` in the general prompt in //// tab | Test -The link text should get translated, the link address should remain unchaged: +The link text should get translated, the link address should remain unchanged: * [Link to heading above](#code-snippets) * [Internal link](index.md#installation){.internal-link target=_blank} diff --git a/docs/en/docs/advanced/custom-response.md b/docs/en/docs/advanced/custom-response.md index 8b4b3da339..e0fafa5dfe 100644 --- a/docs/en/docs/advanced/custom-response.md +++ b/docs/en/docs/advanced/custom-response.md @@ -1,6 +1,6 @@ # 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}. @@ -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`. -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 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 `orjson` and set the response to be `ORJSONResponse`. +By default FastAPI returns JSON responses. -Import the `Response` class (sub-class) you want to use and declare it in the *path operation decorator*. +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. -For large responses, returning a `Response` directly is much faster than returning a dictionary. +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`. -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. +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. -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. +### JSON Performance { #json-performance } -{* ../../docs_src/custom_response/tutorial001b_py310.py hl[2,7] *} +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*. -/// info - -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`. - -And it will be documented as such in OpenAPI. - -/// - -/// tip - -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 } @@ -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. -### `ORJSONResponse` { #orjsonresponse } +/// note | Technical Details -A fast alternative JSON response using `orjson`, as you read above. +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. -/// info - -This requires installing `orjson` for example with `pip install orjson`. - -/// - -### `UJSONResponse` { #ujsonresponse } - -An alternative JSON response using `ujson`. - -/// 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 - -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 } -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] *} +{* ../../docs_src/custom_response/tutorial007_py310.py hl[3,16] *} -#### Using `StreamingResponse` with file-like objects { #using-streamingresponse-with-file-like-objects } +/// note | Technical Details -If you have a file-like object (e.g. the object returned by `open()`), you can create a generator function to iterate over that file-like object. +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. -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. +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. -This includes many libraries to interact with cloud storage, video processing, and others. +This would be even more important with large or infinite streams. -{* ../../docs_src/custom_response/tutorial008_py310.py hl[2,10:12,14] *} - -1. This is the generator function. It's a "generator function" because it contains `yield` statements inside. -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. - - 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 -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. -For example, let's say that you want to use `orjson`, but with some custom settings not used in the included `ORJSONResponse` class. +For example, let's say that you want to use `orjson` 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`. @@ -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. 😉 +### `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 } 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`. -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] *} diff --git a/docs/en/docs/advanced/json-base64-bytes.md b/docs/en/docs/advanced/json-base64-bytes.md new file mode 100644 index 0000000000..c0dfec72b9 --- /dev/null +++ b/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: + +
+ +
+ +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] *} diff --git a/docs/en/docs/advanced/response-directly.md b/docs/en/docs/advanced/response-directly.md index 76cc50d03c..dd0e63c396 100644 --- a/docs/en/docs/advanced/response-directly.md +++ b/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. -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 } -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`. @@ -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. -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 } @@ -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] *} +## 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 } When you return a `Response` directly its data is not validated, converted (serialized), or documented automatically. diff --git a/docs/en/docs/advanced/stream-data.md b/docs/en/docs/advanced/stream-data.md new file mode 100644 index 0000000000..fe9212a24b --- /dev/null +++ b/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 Asyncer, 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] *} diff --git a/docs/en/docs/advanced/strict-content-type.md b/docs/en/docs/advanced/strict-content-type.md new file mode 100644 index 0000000000..54c099410c --- /dev/null +++ b/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. + +/// diff --git a/docs/en/docs/advanced/websockets.md b/docs/en/docs/advanced/websockets.md index ae17742bb5..b3bc9c045b 100644 --- a/docs/en/docs/advanced/websockets.md +++ b/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: -{* ../../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 } 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 @@ -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. -{* ../../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. @@ -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*: -{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} +{* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *} /// 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. -{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} +{* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *} To try it out: diff --git a/docs/en/docs/css/custom.css b/docs/en/docs/css/custom.css index 7c50dbd9be..dc9c7d63b4 100644 --- a/docs/en/docs/css/custom.css +++ b/docs/en/docs/css/custom.css @@ -61,6 +61,10 @@ a.internal-link::after { padding-bottom: 2em; } +.md-footer-meta .md-social { + padding-right: 4rem; +} + .user-list { display: flex; flex-wrap: wrap; diff --git a/docs/en/docs/how-to/general.md b/docs/en/docs/how-to/general.md index 9347192607..4f611dab05 100644 --- a/docs/en/docs/how-to/general.md +++ b/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}. +## 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 } 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}. diff --git a/docs/en/docs/img/tutorial/json-base64-bytes/image01.png b/docs/en/docs/img/tutorial/json-base64-bytes/image01.png new file mode 100644 index 0000000000..996732b749 Binary files /dev/null and b/docs/en/docs/img/tutorial/json-base64-bytes/image01.png differ diff --git a/docs/en/docs/js/init_kapa_widget.js b/docs/en/docs/js/init_kapa_widget.js new file mode 100644 index 0000000000..eaf123bf3f --- /dev/null +++ b/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); +}); diff --git a/docs/en/docs/reference/responses.md b/docs/en/docs/reference/responses.md index bd57861294..2df53e9701 100644 --- a/docs/en/docs/reference/responses.md +++ b/docs/en/docs/reference/responses.md @@ -22,7 +22,13 @@ from fastapi.responses import ( ## 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 options: diff --git a/docs/en/docs/release-notes.md b/docs/en/docs/release-notes.md index 34492bfc58..6d3e2a001d 100644 --- a/docs/en/docs/release-notes.md +++ b/docs/en/docs/release-notes.md @@ -7,13 +7,142 @@ hide: ## 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 + +* 🔨 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). * 📝 Update docs for JWT to prevent timing attacks. PR [#14908](https://github.com/fastapi/fastapi/pull/14908) by [@tiangolo](https://github.com/tiangolo). ### 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 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). @@ -29,6 +158,7 @@ hide: ### 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). * ⬆ 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). diff --git a/docs/en/docs/tutorial/response-model.md b/docs/en/docs/tutorial/response-model.md index 51492722ae..c8312d92c6 100644 --- a/docs/en/docs/tutorial/response-model.md +++ b/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*. * This will be used by the **automatic docs**. * 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: diff --git a/docs/en/docs/tutorial/server-sent-events.md b/docs/en/docs/tutorial/server-sent-events.md new file mode 100644 index 0000000000..0a4bed2660 --- /dev/null +++ b/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 `EventSource` API. + +/// 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 "sentinel" 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 MCP 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 HTML specification: Server-Sent Events. +* 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. 🤓 diff --git a/docs/en/docs/tutorial/stream-json-lines.md b/docs/en/docs/tutorial/stream-json-lines.md new file mode 100644 index 0000000000..2ee3aacc6c --- /dev/null +++ b/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}. 🤓 diff --git a/docs/en/mkdocs.yml b/docs/en/mkdocs.yml index 66ad67e9d0..78f03bf443 100644 --- a/docs/en/mkdocs.yml +++ b/docs/en/mkdocs.yml @@ -154,6 +154,8 @@ nav: - tutorial/cors.md - tutorial/sql-databases.md - tutorial/bigger-applications.md + - tutorial/stream-json-lines.md + - tutorial/server-sent-events.md - tutorial/background-tasks.md - tutorial/metadata.md - tutorial/static-files.md @@ -161,6 +163,7 @@ nav: - tutorial/debugging.md - Advanced User Guide: - advanced/index.md + - advanced/stream-data.md - advanced/path-operation-advanced-configuration.md - advanced/additional-status-codes.md - advanced/response-directly.md @@ -192,6 +195,8 @@ nav: - advanced/wsgi.md - advanced/generate-clients.md - advanced/advanced-python-types.md + - advanced/json-base64-bytes.md + - advanced/strict-content-type.md - fastapi-cli.md - Deployment: - deployment/index.md @@ -342,5 +347,6 @@ extra_css: extra_javascript: - js/termynal.js - js/custom.js +- js/init_kapa_widget.js hooks: - ../../scripts/mkdocs_hooks.py diff --git a/docs/es/docs/advanced/websockets.md b/docs/es/docs/advanced/websockets.md index 2a7fed6c59..e9391c36ca 100644 --- a/docs/es/docs/advanced/websockets.md +++ b/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: -{* ../../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 } 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 @@ -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. -{* ../../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. @@ -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*: -{* ../../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 @@ -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. -{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} +{* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *} Para probarlo: diff --git a/docs/fr/docs/advanced/websockets.md b/docs/fr/docs/advanced/websockets.md index 6f5c3e7033..d78f89c374 100644 --- a/docs/fr/docs/advanced/websockets.md +++ b/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 : -{* ../../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 } 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 @@ -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. -{* ../../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. @@ -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 : -{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} +{* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *} /// 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. -{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} +{* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *} Pour l'essayer : diff --git a/docs/ja/docs/advanced/websockets.md b/docs/ja/docs/advanced/websockets.md index cb5e376de6..efc02079bb 100644 --- a/docs/ja/docs/advanced/websockets.md +++ b/docs/ja/docs/advanced/websockets.md @@ -38,13 +38,13 @@ $ pip install 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 } **FastAPI** アプリケーションで、`websocket` を作成します。 -{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *} /// note | 技術詳細 @@ -58,7 +58,7 @@ $ pip install websockets WebSocketルートでは、メッセージを待機して送信するために `await` を使用できます。 -{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *} バイナリやテキストデータ、JSONデータを送受信できます。 @@ -109,7 +109,7 @@ WebSocketエンドポイントでは、`fastapi` から以下をインポート これらは、他の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 | 情報 @@ -154,7 +154,7 @@ $ fastapi dev main.py WebSocket接続が閉じられると、 `await websocket.receive_text()` は例外 `WebSocketDisconnect` を発生させ、この例のようにキャッチして処理することができます。 -{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} +{* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *} 試してみるには、 diff --git a/docs/ko/docs/advanced/websockets.md b/docs/ko/docs/advanced/websockets.md index 384f93952b..cb59097f6e 100644 --- a/docs/ko/docs/advanced/websockets.md +++ b/docs/ko/docs/advanced/websockets.md @@ -38,13 +38,13 @@ $ pip install 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 } **FastAPI** 애플리케이션에서 `websocket`을 생성합니다: -{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *} /// note | 기술 세부사항 @@ -58,7 +58,7 @@ $ pip install websockets WebSocket 경로에서 메시지를 대기(`await`)하고 전송할 수 있습니다. -{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *} 여러분은 이진 데이터, 텍스트, JSON 데이터를 받을 수 있고 전송할 수 있습니다. @@ -109,7 +109,7 @@ WebSocket 엔드포인트에서 `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 | 정보 @@ -154,7 +154,7 @@ $ fastapi dev main.py WebSocket 연결이 닫히면, `await websocket.receive_text()`가 `WebSocketDisconnect` 예외를 발생시킵니다. 그러면 이 예제처럼 이를 잡아 처리할 수 있습니다. -{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} +{* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *} 테스트해보기: diff --git a/docs/pt/docs/advanced/websockets.md b/docs/pt/docs/advanced/websockets.md index c294b7603e..f148defd4c 100644 --- a/docs/pt/docs/advanced/websockets.md +++ b/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: -{* ../../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 } 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 @@ -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. -{* ../../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. @@ -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*: -{* ../../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 @@ -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. -{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} +{* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *} Para testar: diff --git a/docs/ru/docs/advanced/middleware.md b/docs/ru/docs/advanced/middleware.md index 034feae7eb..1f1a160604 100644 --- a/docs/ru/docs/advanced/middleware.md +++ b/docs/ru/docs/advanced/middleware.md @@ -83,7 +83,7 @@ app.add_middleware(UnicornMiddleware, some_config="rainbow") Поддерживаются следующие аргументы: - `minimum_size` — не сжимать GZip‑ом ответы, размер которых меньше этого минимального значения в байтах. По умолчанию — `500`. -- `compresslevel` — уровень GZip‑сжатия. Целое число от 1 до 9. По умолчанию — `9`. Более низкое значение — быстреее сжатие, но больший размер файла; более высокое значение — более медленное сжатие, но меньший размер файла. +- `compresslevel` — уровень GZip‑сжатия. Целое число от 1 до 9. По умолчанию — `9`. Более низкое значение — быстрее сжатие, но больший размер файла; более высокое значение — более медленное сжатие, но меньший размер файла. ## Другие middleware { #other-middlewares } diff --git a/docs/ru/docs/advanced/websockets.md b/docs/ru/docs/advanced/websockets.md index 446cc2505e..d565d507bc 100644 --- a/docs/ru/docs/advanced/websockets.md +++ b/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` в своем **FastAPI** приложении: -{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *} /// 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 данные. @@ -109,7 +109,7 @@ $ fastapi dev main.py Они работают так же, как и в других FastAPI эндпоинтах/*операциях пути*: -{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} +{* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *} /// info | Примечание @@ -154,7 +154,7 @@ $ fastapi dev main.py Если веб-сокет соединение закрыто, то `await websocket.receive_text()` вызовет исключение `WebSocketDisconnect`, которое можно поймать и обработать как в этом примере: -{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} +{* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *} Чтобы воспроизвести пример: diff --git a/docs/ru/docs/deployment/docker.md b/docs/ru/docs/deployment/docker.md index 791057fe56..5dfa211599 100644 --- a/docs/ru/docs/deployment/docker.md +++ b/docs/ru/docs/deployment/docker.md @@ -214,7 +214,7 @@ CMD ["fastapi", "run", "app/main.py", "--port", "80"] 5. Копируем директорию `./app` внутрь директории `/code`. - Так как здесь весь код, который **меняется чаще всего**, кэш Docker **вряд ли** будет использоваться для этого шагa или **последующих шагов**. + Так как здесь весь код, который **меняется чаще всего**, кэш Docker **вряд ли** будет использоваться для этого шага или **последующих шагов**. Поэтому важно разместить этот шаг **ближе к концу** `Dockerfile`, чтобы оптимизировать время сборки образа контейнера. diff --git a/docs/ru/docs/history-design-future.md b/docs/ru/docs/history-design-future.md index e2395fe8b9..5019157600 100644 --- a/docs/ru/docs/history-design-future.md +++ b/docs/ru/docs/history-design-future.md @@ -76,4 +76,4 @@ У **FastAPI** великое будущее. -И [ваш вклад в это](help-fastapi.md){.internal-link target=_blank} - очень ценнен. +И [ваш вклад в это](help-fastapi.md){.internal-link target=_blank} - очень ценен. diff --git a/docs/ru/docs/tutorial/security/oauth2-jwt.md b/docs/ru/docs/tutorial/security/oauth2-jwt.md index 7838b07df4..f7853d48f7 100644 --- a/docs/ru/docs/tutorial/security/oauth2-jwt.md +++ b/docs/ru/docs/tutorial/security/oauth2-jwt.md @@ -20,7 +20,7 @@ eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4 Но он подписан. Следовательно, когда вы получаете токен, который вы эмитировали (выдавали), вы можете убедиться, что это именно вы его эмитировали. -Таким образом, можно создать токен со сроком действия, скажем, 1 неделя. А когда пользователь вернется на следующий день с тем же токеном, вы будете знать, что он все еще авторизирован в вашей системе. +Таким образом, можно создать токен со сроком действия, скажем, 1 неделя. А когда пользователь вернется на следующий день с тем же токеном, вы будете знать, что он все еще авторизован в вашей системе. Через неделю срок действия токена истечет, пользователь не будет авторизован и ему придется заново входить в систему, чтобы получить новый токен. А если пользователь (или третье лицо) попытается модифицировать токен, чтобы изменить срок действия, вы сможете это обнаружить, поскольку подписи не будут совпадать. diff --git a/docs/tr/docs/advanced/websockets.md b/docs/tr/docs/advanced/websockets.md index 16b4be7e8d..a5ab27597e 100644 --- a/docs/tr/docs/advanced/websockets.md +++ b/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: -{* ../../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 } **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 @@ -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. -{* ../../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. @@ -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: -{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} +{* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *} /// 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. -{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} +{* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *} Denemek için: diff --git a/docs/uk/docs/advanced/websockets.md b/docs/uk/docs/advanced/websockets.md index 21bb61d7e9..bb06ac00b7 100644 --- a/docs/uk/docs/advanced/websockets.md +++ b/docs/uk/docs/advanced/websockets.md @@ -38,13 +38,13 @@ $ pip install 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 } У вашому застосунку **FastAPI** створіть `websocket`: -{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *} /// note | Технічні деталі @@ -58,7 +58,7 @@ $ pip install websockets У вашому маршруті WebSocket ви можете `await` повідомлення і надсилати повідомлення. -{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *} Ви можете отримувати та надсилати бінарні, текстові та JSON-дані. @@ -109,7 +109,7 @@ $ fastapi dev main.py Вони працюють так само, як для інших ендпойнтів FastAPI/*операцій шляху*: -{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} +{* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *} /// info @@ -154,7 +154,7 @@ $ fastapi dev main.py Коли з'єднання WebSocket закривається, `await websocket.receive_text()` підніме виняток `WebSocketDisconnect`, який ви можете перехопити й обробити, як у цьому прикладі. -{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} +{* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *} Щоб спробувати: diff --git a/docs/zh-hant/docs/advanced/websockets.md b/docs/zh-hant/docs/advanced/websockets.md index c4e904f6f6..22e3fdcb9e 100644 --- a/docs/zh-hant/docs/advanced/websockets.md +++ b/docs/zh-hant/docs/advanced/websockets.md @@ -38,13 +38,13 @@ $ pip install websockets 但這是能讓我們專注於 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 } 在你的 **FastAPI** 應用中,建立一個 `websocket`: -{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *} /// note | 技術細節 @@ -58,7 +58,7 @@ $ pip install websockets 在你的 WebSocket 路由中,你可以 `await` 接收訊息並傳送訊息。 -{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *} 你可以接收與傳送二進位、文字與 JSON 資料。 @@ -109,7 +109,7 @@ $ fastapi dev main.py 它們的運作方式與其他 FastAPI 端點/*路徑操作* 相同: -{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} +{* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *} /// info @@ -154,7 +154,7 @@ $ fastapi dev main.py 當 WebSocket 連線關閉時,`await websocket.receive_text()` 會拋出 `WebSocketDisconnect` 例外,你可以像範例中那樣捕捉並處理。 -{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} +{* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *} 試用方式: diff --git a/docs/zh/docs/advanced/websockets.md b/docs/zh/docs/advanced/websockets.md index 513e1aaecd..a4cdae3a22 100644 --- a/docs/zh/docs/advanced/websockets.md +++ b/docs/zh/docs/advanced/websockets.md @@ -38,13 +38,13 @@ $ pip install 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 } 在您的 **FastAPI** 应用程序中,创建一个 `websocket`: -{* ../../docs_src/websockets/tutorial001_py310.py hl[1,46:47] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[1,46:47] *} /// note | 技术细节 @@ -58,7 +58,7 @@ $ pip install websockets 在您的 WebSocket 路由中,您可以使用 `await` 等待消息并发送消息。 -{* ../../docs_src/websockets/tutorial001_py310.py hl[48:52] *} +{* ../../docs_src/websockets_/tutorial001_py310.py hl[48:52] *} 您可以接收和发送二进制、文本和 JSON 数据。 @@ -109,7 +109,7 @@ $ fastapi dev main.py 它们的工作方式与其他 FastAPI 端点/*路径操作* 相同: -{* ../../docs_src/websockets/tutorial002_an_py310.py hl[68:69,82] *} +{* ../../docs_src/websockets_/tutorial002_an_py310.py hl[68:69,82] *} /// info @@ -154,7 +154,7 @@ $ fastapi dev main.py 当 WebSocket 连接关闭时,`await websocket.receive_text()` 将引发 `WebSocketDisconnect` 异常,您可以捕获并处理该异常,就像本示例中的示例一样。 -{* ../../docs_src/websockets/tutorial003_py310.py hl[79:81] *} +{* ../../docs_src/websockets_/tutorial003_py310.py hl[79:81] *} 尝试以下操作: diff --git a/docs_src/custom_response/tutorial007_py310.py b/docs_src/custom_response/tutorial007_py310.py index e2a53a2119..f8dd9f895d 100644 --- a/docs_src/custom_response/tutorial007_py310.py +++ b/docs_src/custom_response/tutorial007_py310.py @@ -1,3 +1,4 @@ +import anyio from fastapi import FastAPI from fastapi.responses import StreamingResponse @@ -7,6 +8,7 @@ app = FastAPI() async def fake_video_streamer(): for i in range(10): yield b"some fake video bytes" + await anyio.sleep(0) @app.get("/") diff --git a/docs_src/custom_response/tutorial010_py310.py b/docs_src/custom_response/tutorial010_py310.py index 57cb062604..d5bc783aa0 100644 --- a/docs_src/custom_response/tutorial010_py310.py +++ b/docs_src/custom_response/tutorial010_py310.py @@ -1,9 +1,9 @@ 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/") async def read_items(): - return [{"item_id": "Foo"}] + return "

Items

This is a list of items.

" diff --git a/docs_src/websockets/__init__.py b/docs_src/json_base64_bytes/__init__.py similarity index 100% rename from docs_src/websockets/__init__.py rename to docs_src/json_base64_bytes/__init__.py diff --git a/docs_src/json_base64_bytes/tutorial001_py310.py b/docs_src/json_base64_bytes/tutorial001_py310.py new file mode 100644 index 0000000000..3262ffb7f0 --- /dev/null +++ b/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 diff --git a/docs_src/python_types/tutorial005_py39.py b/docs_src/python_types/tutorial005_py39.py deleted file mode 100644 index 6c8edb0ec4..0000000000 --- a/docs_src/python_types/tutorial005_py39.py +++ /dev/null @@ -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 diff --git a/docs_src/server_sent_events/__init__.py b/docs_src/server_sent_events/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/server_sent_events/tutorial001_py310.py b/docs_src/server_sent_events/tutorial001_py310.py new file mode 100644 index 0000000000..8fa470da50 --- /dev/null +++ b/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 diff --git a/docs_src/server_sent_events/tutorial002_py310.py b/docs_src/server_sent_events/tutorial002_py310.py new file mode 100644 index 0000000000..0f6136f4fd --- /dev/null +++ b/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) diff --git a/docs_src/server_sent_events/tutorial003_py310.py b/docs_src/server_sent_events/tutorial003_py310.py new file mode 100644 index 0000000000..3006deb86d --- /dev/null +++ b/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) diff --git a/docs_src/server_sent_events/tutorial004_py310.py b/docs_src/server_sent_events/tutorial004_py310.py new file mode 100644 index 0000000000..3e8f8d113f --- /dev/null +++ b/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)) diff --git a/docs_src/server_sent_events/tutorial005_py310.py b/docs_src/server_sent_events/tutorial005_py310.py new file mode 100644 index 0000000000..4e6730e5aa --- /dev/null +++ b/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") diff --git a/docs_src/stream_data/__init__.py b/docs_src/stream_data/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/stream_data/tutorial001_py310.py b/docs_src/stream_data/tutorial001_py310.py new file mode 100644 index 0000000000..2e91ec9ac9 --- /dev/null +++ b/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") diff --git a/docs_src/stream_data/tutorial002_py310.py b/docs_src/stream_data/tutorial002_py310.py new file mode 100644 index 0000000000..aa8bcee3a9 --- /dev/null +++ b/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 diff --git a/docs_src/stream_json_lines/__init__.py b/docs_src/stream_json_lines/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/stream_json_lines/tutorial001_py310.py b/docs_src/stream_json_lines/tutorial001_py310.py new file mode 100644 index 0000000000..4fbe7c69cc --- /dev/null +++ b/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 diff --git a/docs_src/strict_content_type/__init__.py b/docs_src/strict_content_type/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/strict_content_type/tutorial001_py310.py b/docs_src/strict_content_type/tutorial001_py310.py new file mode 100644 index 0000000000..a44f4b1386 --- /dev/null +++ b/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 diff --git a/docs_src/websockets_/__init__.py b/docs_src/websockets_/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs_src/websockets/tutorial001_py310.py b/docs_src/websockets_/tutorial001_py310.py similarity index 100% rename from docs_src/websockets/tutorial001_py310.py rename to docs_src/websockets_/tutorial001_py310.py diff --git a/docs_src/websockets/tutorial002_an_py310.py b/docs_src/websockets_/tutorial002_an_py310.py similarity index 100% rename from docs_src/websockets/tutorial002_an_py310.py rename to docs_src/websockets_/tutorial002_an_py310.py diff --git a/docs_src/websockets/tutorial002_py310.py b/docs_src/websockets_/tutorial002_py310.py similarity index 100% rename from docs_src/websockets/tutorial002_py310.py rename to docs_src/websockets_/tutorial002_py310.py diff --git a/docs_src/websockets/tutorial003_py310.py b/docs_src/websockets_/tutorial003_py310.py similarity index 100% rename from docs_src/websockets/tutorial003_py310.py rename to docs_src/websockets_/tutorial003_py310.py diff --git a/fastapi/.agents/skills/fastapi/SKILL.md b/fastapi/.agents/skills/fastapi/SKILL.md new file mode 100644 index 0000000000..48cfdabb87 --- /dev/null +++ b/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 [] +``` diff --git a/fastapi/.agents/skills/fastapi/references/dependencies.md b/fastapi/.agents/skills/fastapi/references/dependencies.md new file mode 100644 index 0000000000..ca709096e6 --- /dev/null +++ b/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() +``` diff --git a/fastapi/.agents/skills/fastapi/references/other-tools.md b/fastapi/.agents/skills/fastapi/references/other-tools.md new file mode 100644 index 0000000000..58b19d0966 --- /dev/null +++ b/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. diff --git a/fastapi/.agents/skills/fastapi/references/streaming.md b/fastapi/.agents/skills/fastapi/references/streaming.md new file mode 100644 index 0000000000..0832eedcb9 --- /dev/null +++ b/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()) +``` diff --git a/fastapi/__init__.py b/fastapi/__init__.py index de5a0be382..06dacbd9dc 100644 --- a/fastapi/__init__.py +++ b/fastapi/__init__.py @@ -1,6 +1,6 @@ """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 diff --git a/fastapi/_compat/v2.py b/fastapi/_compat/v2.py index b83bc1b55b..79fba93188 100644 --- a/fastapi/_compat/v2.py +++ b/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.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_core import CoreSchema as CoreSchema from pydantic_core import PydanticUndefined @@ -40,6 +40,23 @@ RequiredParam = PydanticUndefined Undefined = PydanticUndefined 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 _Attrs = { "default": ..., @@ -182,6 +199,32 @@ class ModelField: 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: # Each ModelField is unique for our purposes, to allow making a dict from # ModelField to its JSON Schema. diff --git a/fastapi/applications.py b/fastapi/applications.py index 41d86143ec..e7e816c2e9 100644 --- a/fastapi/applications.py +++ b/fastapi/applications.py @@ -840,6 +840,29 @@ class FastAPI(Starlette): """ ), ] = 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[ Any, Doc( @@ -974,6 +997,7 @@ class FastAPI(Starlette): include_in_schema=include_in_schema, responses=responses, generate_unique_id_function=generate_unique_id_function, + strict_content_type=strict_content_type, ) self.exception_handlers: dict[ Any, Callable[[Request, Any], Response | Awaitable[Response]] @@ -1077,16 +1101,18 @@ class FastAPI(Starlette): def setup(self) -> None: 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: root_path = req.scope.get("root_path", "").rstrip("/") - if root_path not in server_urls: - if root_path and self.root_path_in_servers: - self.servers.insert(0, {"url": root_path}) - server_urls.add(root_path) - return JSONResponse(self.openapi()) + schema = self.openapi() + if root_path and self.root_path_in_servers: + server_urls = {s.get("url") for s in schema.get("servers", [])} + if root_path not in server_urls: + 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) if self.openapi_url and self.docs_url: diff --git a/fastapi/datastructures.py b/fastapi/datastructures.py index c04b5f0f39..479e1a7c3b 100644 --- a/fastapi/datastructures.py +++ b/fastapi/datastructures.py @@ -139,7 +139,7 @@ class UploadFile(StarletteUploadFile): def __get_pydantic_json_schema__( cls, core_schema: Mapping[str, Any], handler: GetJsonSchemaHandler ) -> dict[str, Any]: - return {"type": "string", "format": "binary"} + return {"type": "string", "contentMediaType": "application/octet-stream"} @classmethod def __get_pydantic_core_schema__( diff --git a/fastapi/dependencies/utils.py b/fastapi/dependencies/utils.py index ab18ec2db6..8fcf1a5b3c 100644 --- a/fastapi/dependencies/utils.py +++ b/fastapi/dependencies/utils.py @@ -1,7 +1,17 @@ import dataclasses import inspect 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 copy import copy, deepcopy from dataclasses import dataclass @@ -251,6 +261,26 @@ def get_typed_return_annotation(call: Callable[..., Any]) -> Any: 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( *, path: str, diff --git a/fastapi/openapi/docs.py b/fastapi/openapi/docs.py index b845f87c1c..0d9242f9fa 100644 --- a/fastapi/openapi/docs.py +++ b/fastapi/openapi/docs.py @@ -5,6 +5,20 @@ from annotated_doc import Doc from fastapi.encoders import jsonable_encoder 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 " + html = get_swagger_ui_html( + openapi_url="/openapi.json", + title="Test", + init_oauth={"appName": xss_payload}, + ) + body = html.body.decode() + + assert "