Compare commits

...
31 Commits
Author SHA1 Message Date
Sebastián Ramírezandgithub-actions[bot] 7ac0f1b541 🔖 Release version 0.140.4 (#16070)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-27 15:45:50 +00:00
github-actions[bot] 44e4eeaa78 📝 Update release notes
[skip ci]
2026-07-27 15:36:07 +00:00
Sebastián Ramírez 7134121a71 ️ Skip unused dependency repeat bookkeeping (#16069) 2026-07-27 15:35:29 +00:00
Sebastián Ramírezandgithub-actions[bot] 415d37fa6f 🔖 Release version 0.140.3 (#16068)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-27 15:16:54 +00:00
github-actions[bot] ca353d7215 📝 Update release notes
[skip ci]
2026-07-27 15:09:32 +00:00
Sebastián Ramírez d012979f9a ️ Avoid repeated dependency flattening in OpenAPI (#16067) 2026-07-27 15:08:45 +00:00
Sebastián Ramírezandgithub-actions[bot] 051dfeddcc 🔖 Release version 0.140.2 (#16066)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-27 14:14:11 +00:00
github-actions[bot] e9980492f2 📝 Update release notes
[skip ci]
2026-07-27 14:07:09 +00:00
Sebastián RamírezandMarcelo Trylesinski 8069eadf5e ️ Stop retaining flat dependency trees (#16065)
Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
2026-07-27 16:06:31 +02:00
github-actions[bot] 2b4349d6c2 📝 Update release notes
[skip ci]
2026-07-27 12:40:07 +00:00
Sebastián Ramírez 2f34b90742 👷 Add new memory benchmark (#16064) 2026-07-27 12:39:36 +00:00
Sebastián Ramírezandgithub-actions[bot] 3e310c90fa 🔖 Release version 0.140.1 (#16063)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-27 14:06:27 +02:00
github-actions[bot] 43eafc6a28 📝 Update release notes
[skip ci]
2026-07-27 11:18:58 +00:00
Sebastián Ramírez 65ef53ae6a ♻️ Update the lru_cache limit for dependencies to account for large apps (#16062) 2026-07-27 11:18:26 +00:00
Sebastián Ramírezandgithub-actions[bot] 255b912928 🔖 Release version 0.140.0 (#16050)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-24 21:15:37 +00:00
github-actions[bot] 892eacd27d 📝 Update release notes
[skip ci]
2026-07-24 21:08:24 +00:00
Sebastián Ramírez 0270829500 ️ Reduce memory usage in dependencies (#16049) 2026-07-24 21:07:51 +00:00
github-actions[bot] ae031be7b5 📝 Update release notes
[skip ci]
2026-07-24 20:30:21 +00:00
Yurii MotovandSebastián Ramírez f3644b33cd 📝 Fix links in docs (#15967)
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2026-07-24 20:29:32 +00:00
github-actions[bot] 1e24ca0e12 📝 Update release notes
[skip ci]
2026-07-24 20:22:30 +00:00
Sebastián Ramírez 513c396322 👷 Add CI memory benchmark (#16046) 2026-07-24 20:21:58 +00:00
github-actions[bot] a64dfbbd21 📝 Update release notes
[skip ci]
2026-07-23 18:53:07 +00:00
Sebastián Ramírez 4f155486f0 📝 Add Library Skills documentation (#16041) 2026-07-23 20:52:31 +02:00
github-actions[bot] 704fbe1439 📝 Update release notes
[skip ci]
2026-07-21 21:28:18 +00:00
Sebastián Ramírez 1c83302d78 📝 Update docs to use uv projects by default (#16032) 2026-07-21 21:27:36 +00:00
github-actions[bot] 7d210a4a9f 📝 Update release notes
[skip ci]
2026-07-21 06:25:05 +00:00
Sebastián Ramírezandgithub-actions[bot] 345d44b00f 👥 Update FastAPI People - Sponsors (#16027)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-21 08:24:29 +02:00
github-actions[bot] afe41126f6 📝 Update release notes
[skip ci]
2026-07-16 18:39:08 +00:00
Sebastián Ramírez 7f05e26d08 🔥 Remove now-obsolete scripts to generate data for FastAPI People (#16016) 2026-07-16 18:38:36 +00:00
github-actions[bot] efcf8a1830 📝 Update release notes
[skip ci]
2026-07-16 18:25:46 +00:00
Sebastián Ramírez e735449bae 📝 Restructure FastAPI People and related pages (#16015) 2026-07-16 20:25:14 +02:00
85 changed files with 1142 additions and 2743 deletions

No files matched your search

-56
View File
@@ -1,56 +0,0 @@
name: FastAPI People Contributors
on:
schedule:
- cron: "0 3 1 * *"
workflow_dispatch:
inputs:
debug_enabled:
description: "Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)"
required: false
default: "false"
permissions: {}
jobs:
job:
if: github.repository_owner == 'fastapi'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: true # Required for `git push` in `contributors.py`
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
with:
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
version: "0.11.18"
enable-cache: true
cache-dependency-glob: |
pyproject.toml
uv.lock
- name: Install Dependencies
run: uv sync --locked --no-dev --group github-actions
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@35b54afac29c97fb54faba5b513f8fbd1882f113 # v3.24
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
with:
limit-access-to-actor: true
env:
GITHUB_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }} # zizmor: ignore[secrets-outside-env]
- name: FastAPI People Contributors
run: uv run ./scripts/contributors.py
env:
GITHUB_TOKEN: ${{ secrets.FASTAPI_PR_TOKEN }} # zizmor: ignore[secrets-outside-env]
-57
View File
@@ -1,57 +0,0 @@
name: FastAPI People
on:
schedule:
- cron: "0 14 1 * *"
workflow_dispatch:
inputs:
debug_enabled:
description: Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)
required: false
default: "false"
permissions: {}
jobs:
job:
if: github.repository_owner == 'fastapi'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: true # Required for `git push` in `people.py`
- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version-file: ".python-version"
- name: Setup uv
uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v8.3.0
with:
# Before upgrading uv version, make sure astral-sh/setup-uv knows its checksum.
# See: https://github.com/astral-sh/setup-uv/issues/851#issuecomment-4282017837
version: "0.11.18"
enable-cache: true
cache-dependency-glob: |
pyproject.toml
uv.lock
- name: Install Dependencies
run: uv sync --locked --no-dev --group github-actions
# Allow debugging with tmate
- name: Setup tmate session
uses: mxschmitt/action-tmate@35b54afac29c97fb54faba5b513f8fbd1882f113 # v3.24
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
with:
limit-access-to-actor: true
env:
GITHUB_TOKEN: ${{ secrets.FASTAPI_PEOPLE }} # zizmor: ignore[secrets-outside-env]
- name: FastAPI People Experts
run: uv run ./scripts/people.py
env:
GITHUB_TOKEN: ${{ secrets.FASTAPI_PEOPLE }} # zizmor: ignore[secrets-outside-env]
SLEEP_INTERVAL: ${{ vars.PEOPLE_SLEEP_INTERVAL }}
+5
View File
@@ -195,6 +195,11 @@ jobs:
with:
mode: simulation
run: uv run --no-sync pytest tests/benchmarks --codspeed
- name: CodSpeed memory benchmark
uses: CodSpeedHQ/action@a4a36bb07c0638b0b4ca52bf1f3dad1b4289e52f # v4.18.1
with:
mode: memory
run: uv run --no-sync pytest tests/memory_benchmarks --codspeed
coverage-combine:
needs:
+18 -16
View File
@@ -87,7 +87,7 @@ The key features are:
"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_"
<div style="text-align: right; margin-right: 10%;">Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - <strong>Uber</strong> <a href="https://eng.uber.com/ludwig-v0-2/"><small>(ref)</small></a></div>
<div style="text-align: right; margin-right: 10%;">Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - <strong>Uber</strong> <a href="https://www.uber.com/us/en/blog/ludwig-v0-2/"><small>(ref)</small></a></div>
---
@@ -129,17 +129,17 @@ If you are building a <abbr title="Command Line Interface">CLI</abbr> app to be
FastAPI stands on the shoulders of giants:
* [Starlette](https://www.starlette.dev/) for the web parts.
* [Pydantic](https://docs.pydantic.dev/) for the data parts.
* [Starlette](https://starlette.dev/) for the web parts.
* [Pydantic](https://pydantic.dev/docs/) for the data parts.
## Installation
Create and activate a [virtual environment](https://fastapi.tiangolo.com/virtual-environments/) and then install FastAPI:
First, [install `uv`](https://docs.astral.sh/uv/getting-started/installation/), and then add FastAPI to your project:
<div class="termy">
```console
$ pip install "fastapi[standard]"
$ uv add "fastapi[standard]"
---> 100%
```
@@ -148,6 +148,8 @@ $ pip install "fastapi[standard]"
**Note**: Make sure you put `"fastapi[standard]"` in quotes to ensure it works in all terminals.
If you prefer to use `pip`, install `fastapi[standard]` inside a virtual environment. See the [installation guide](tutorial/#install-fastapi) for the alternative steps.
## Example
### Create it
@@ -204,7 +206,7 @@ Run the server with:
<div class="termy">
```console
$ fastapi dev
$ uv run fastapi dev
╭────────── FastAPI CLI - Development mode ───────────╮
│ │
@@ -231,7 +233,7 @@ INFO: Application startup complete.
<details markdown="1">
<summary>About the command <code>fastapi dev</code>...</summary>
The command `fastapi dev` reads your `main.py` file automatically, detects the **FastAPI** app in it, and starts a server using [Uvicorn](https://www.uvicorn.dev).
The command `fastapi dev` reads your `main.py` file automatically, detects the **FastAPI** app in it, and starts a server using [Uvicorn](https://uvicorn.dev).
By default, `fastapi dev` will start with auto-reload enabled for local development.
@@ -268,7 +270,7 @@ You will see the automatic interactive API documentation (provided by [Swagger U
And now, go to [http://127.0.0.1:8000/redoc](http://127.0.0.1:8000/redoc).
You will see the alternative automatic documentation (provided by [ReDoc](https://github.com/Rebilly/ReDoc)):
You will see the alternative automatic documentation (provided by [ReDoc](https://github.com/Redocly/redoc)):
![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png)
@@ -451,7 +453,7 @@ You can optionally deploy your FastAPI app to [FastAPI Cloud](https://fastapiclo
<div class="termy">
```console
$ fastapi deploy
$ uv run fastapi deploy
Deploying to FastAPI Cloud...
@@ -494,7 +496,7 @@ FastAPI depends on Pydantic and Starlette.
### `standard` Dependencies
When you install FastAPI with `pip install "fastapi[standard]"` it comes with the `standard` group of optional dependencies:
When you install FastAPI with `uv add "fastapi[standard]"` it comes with the `standard` group of optional dependencies:
Used by Pydantic:
@@ -508,17 +510,17 @@ Used by Starlette:
Used by FastAPI:
* [`uvicorn`](https://www.uvicorn.dev) - for the server that loads and serves your application. This includes `uvicorn[standard]`, which includes some dependencies (e.g. `uvloop`) needed for high performance serving.
* [`uvicorn`](https://uvicorn.dev) - for the server that loads and serves your application. This includes `uvicorn[standard]`, which includes some dependencies (e.g. `uvloop`) needed for high performance serving.
* `fastapi-cli[standard]` - to provide the `fastapi` command.
* This includes `fastapi-cloud-cli`, which allows you to deploy your FastAPI application to [FastAPI Cloud](https://fastapicloud.com).
### Without `standard` Dependencies
If you don't want to include the `standard` optional dependencies, you can install with `pip install fastapi` instead of `pip install "fastapi[standard]"`.
If you don't want to include the `standard` optional dependencies, you can install with `uv add fastapi` instead of `uv add "fastapi[standard]"`.
### Without `fastapi-cloud-cli`
If you want to install FastAPI with the standard dependencies but without the `fastapi-cloud-cli`, you can install with `pip install "fastapi[standard-no-fastapi-cloud-cli]"`.
If you want to install FastAPI with the standard dependencies but without the `fastapi-cloud-cli`, you can install with `uv add "fastapi[standard-no-fastapi-cloud-cli]"`.
### Additional Optional Dependencies
@@ -526,13 +528,13 @@ There are some additional dependencies you might want to install.
Additional optional Pydantic dependencies:
* [`pydantic-settings`](https://docs.pydantic.dev/latest/usage/pydantic_settings/) - for settings management.
* [`pydantic-extra-types`](https://docs.pydantic.dev/latest/usage/types/extra_types/extra_types/) - for extra types to be used with Pydantic.
* [`pydantic-settings`](https://pydantic.dev/docs/validation/latest/concepts/pydantic_settings/) - for settings management.
* [`pydantic-extra-types`](https://github.com/pydantic/pydantic-extra-types) - for extra types to be used with Pydantic.
Additional optional FastAPI dependencies:
* [`orjson`](https://github.com/ijl/orjson) - Required if you want to use `ORJSONResponse`.
* [`ujson`](https://github.com/esnme/ultrajson) - Required if you want to use `UJSONResponse`.
* [`ujson`](https://github.com/ultrajson/ultrajson) - Required if you want to use `UJSONResponse`.
## License
+111 -147
View File
@@ -11,9 +11,6 @@ sponsors:
- login: coderabbitai
avatarUrl: https://avatars.githubusercontent.com/u/132028505?v=4
url: https://github.com/coderabbitai
- login: zuplo
avatarUrl: https://avatars.githubusercontent.com/u/85497839?v=4
url: https://github.com/zuplo
- login: blockbee-io
avatarUrl: https://avatars.githubusercontent.com/u/115143449?u=1b8620c2d6567c4df2111a371b85a51f448f9b85&v=4
url: https://github.com/blockbee-io
@@ -23,12 +20,12 @@ sponsors:
- login: railwayapp
avatarUrl: https://avatars.githubusercontent.com/u/66716858?v=4
url: https://github.com/railwayapp
- - login: speakeasy-api
avatarUrl: https://avatars.githubusercontent.com/u/91446104?v=4
url: https://github.com/speakeasy-api
- login: stainless-api
avatarUrl: https://avatars.githubusercontent.com/u/88061651?v=4
url: https://github.com/stainless-api
- - login: dribia
avatarUrl: https://avatars.githubusercontent.com/u/41189616?v=4
url: https://github.com/dribia
- login: BairesDev-LLC
avatarUrl: https://avatars.githubusercontent.com/u/133211198?u=c1462ade28fe251414bfedc28ce3f10242d44843&v=4
url: https://github.com/BairesDev-LLC
- login: svix
avatarUrl: https://avatars.githubusercontent.com/u/80175132?v=4
url: https://github.com/svix
@@ -38,19 +35,22 @@ sponsors:
- login: databento
avatarUrl: https://avatars.githubusercontent.com/u/64141749?v=4
url: https://github.com/databento
- login: tutorcruncher
avatarUrl: https://avatars.githubusercontent.com/u/3341959?v=4
url: https://github.com/tutorcruncher
- - login: LambdaTest-Inc
avatarUrl: https://avatars.githubusercontent.com/u/171592363?u=96606606a45fa170427206199014f2a5a2a4920b&v=4
avatarUrl: https://avatars.githubusercontent.com/u/171592363?u=080d9ba6069d0ff2a0558825ff2f667c45807687&v=4
url: https://github.com/LambdaTest-Inc
- login: Ponte-Energy-Partners
avatarUrl: https://avatars.githubusercontent.com/u/114745848?v=4
url: https://github.com/Ponte-Energy-Partners
- login: BoostryJP
avatarUrl: https://avatars.githubusercontent.com/u/57932412?v=4
url: https://github.com/BoostryJP
- login: acsone
avatarUrl: https://avatars.githubusercontent.com/u/7601056?v=4
url: https://github.com/acsone
- - login: scalar
- - login: manulife-ai
avatarUrl: https://avatars.githubusercontent.com/u/195145621?v=4
url: https://github.com/manulife-ai
- login: scalar
avatarUrl: https://avatars.githubusercontent.com/u/301879?v=4
url: https://github.com/scalar
- login: Trivie
@@ -62,9 +62,6 @@ sponsors:
- login: Doist
avatarUrl: https://avatars.githubusercontent.com/u/2565372?v=4
url: https://github.com/Doist
- - login: mainframeindustries
avatarUrl: https://avatars.githubusercontent.com/u/55092103?v=4
url: https://github.com/mainframeindustries
- - login: alixlahuec
avatarUrl: https://avatars.githubusercontent.com/u/29543316?u=44357eb2a93bccf30fb9d389b8befe94a3d00985&v=4
url: https://github.com/alixlahuec
@@ -77,9 +74,9 @@ sponsors:
- login: ChargeStorm
avatarUrl: https://avatars.githubusercontent.com/u/26000165?v=4
url: https://github.com/ChargeStorm
- login: ibrahimpelumi6142
avatarUrl: https://avatars.githubusercontent.com/u/113442282?v=4
url: https://github.com/ibrahimpelumi6142
- login: DanielYang59
avatarUrl: https://avatars.githubusercontent.com/u/80093591?u=63873f701c7c74aac83c906800a1dddc0bc8c92f&v=4
url: https://github.com/DanielYang59
- login: nilslindemann
avatarUrl: https://avatars.githubusercontent.com/u/6892179?u=1dca6a22195d6cd1ab20737c0e19a4c55d639472&v=4
url: https://github.com/nilslindemann
@@ -89,33 +86,30 @@ sponsors:
- login: otosky
avatarUrl: https://avatars.githubusercontent.com/u/42260747?u=69d089387c743d89427aa4ad8740cfb34045a9e0&v=4
url: https://github.com/otosky
- login: ramonalmeidam
avatarUrl: https://avatars.githubusercontent.com/u/45269580?u=3358750b3a5854d7c3ed77aaca7dd20a0f529d32&v=4
url: https://github.com/ramonalmeidam
- login: roboflow
avatarUrl: https://avatars.githubusercontent.com/u/53104118?v=4
url: https://github.com/roboflow
- login: dudikbender
avatarUrl: https://avatars.githubusercontent.com/u/53487583?u=3a57542938ebfd57579a0111db2b297e606d9681&v=4
url: https://github.com/dudikbender
- login: ehaca
avatarUrl: https://avatars.githubusercontent.com/u/25950317?u=cec1a3e0643b785288ae8260cc295a85ab344995&v=4
url: https://github.com/ehaca
- login: raphaellaude
avatarUrl: https://avatars.githubusercontent.com/u/28026311?u=91e1c00d9ac4f8045527e13de8050d504531cbc0&v=4
url: https://github.com/raphaellaude
- login: timlrx
avatarUrl: https://avatars.githubusercontent.com/u/28362229?u=9a745ca31372ee324af682715ae88ce8522f9094&v=4
url: https://github.com/timlrx
- login: Leay15
avatarUrl: https://avatars.githubusercontent.com/u/32212558?u=c4aa9c1737e515959382a5515381757b1fd86c53&v=4
url: https://github.com/Leay15
- login: timlrx
avatarUrl: https://avatars.githubusercontent.com/u/28362229?u=9a745ca31372ee324af682715ae88ce8522f9094&v=4
url: https://github.com/timlrx
- login: ehaca
avatarUrl: https://avatars.githubusercontent.com/u/25950317?u=cec1a3e0643b785288ae8260cc295a85ab344995&v=4
url: https://github.com/ehaca
- login: RaamEEIL
avatarUrl: https://avatars.githubusercontent.com/u/20320552?v=4
url: https://github.com/RaamEEIL
- login: ashi-agrawal
avatarUrl: https://avatars.githubusercontent.com/u/17105294?u=99c7a854035e5398d8e7b674f2d42baae6c957f8&v=4
url: https://github.com/ashi-agrawal
- login: jaredtrog
avatarUrl: https://avatars.githubusercontent.com/u/4381365?v=4
url: https://github.com/jaredtrog
- login: jugeeem
avatarUrl: https://avatars.githubusercontent.com/u/116043716?u=ae590d79c38ac79c91b9c5caa6887d061e865a3d&v=4
avatarUrl: https://avatars.githubusercontent.com/u/116043716?u=e4df530e99a086a1085f3dc125b94783670fb383&v=4
url: https://github.com/jugeeem
- login: Karine-Bauch
avatarUrl: https://avatars.githubusercontent.com/u/90465103?u=7feb1018abb1a5631cfd9a91fea723d1ceb5f49b&v=4
url: https://github.com/Karine-Bauch
- login: Charisn
avatarUrl: https://avatars.githubusercontent.com/u/100683386?u=5a57e569b443a58cb34a32b6cb6ea12c783e14c1&v=4
url: https://github.com/Charisn
- login: kaoru0310
avatarUrl: https://avatars.githubusercontent.com/u/80977929?u=1b61d10142b490e56af932ddf08a390fae8ee94f&v=4
url: https://github.com/kaoru0310
@@ -128,20 +122,17 @@ sponsors:
- login: anthonycepeda
avatarUrl: https://avatars.githubusercontent.com/u/72019805?u=60bdf46240cff8fca482ff0fc07d963fd5e1a27c&v=4
url: https://github.com/anthonycepeda
- login: AalbatrossGuy
avatarUrl: https://avatars.githubusercontent.com/u/68378354?u=0bdeea9356d24f638244131f6d8d1e2d2f3601ca&v=4
url: https://github.com/AalbatrossGuy
- login: patsatsia
avatarUrl: https://avatars.githubusercontent.com/u/61111267?u=3271b85f7a37b479c8d0ae0a235182e83c166edf&v=4
url: https://github.com/patsatsia
- login: oliverxchen
avatarUrl: https://avatars.githubusercontent.com/u/4471774?u=534191f25e32eeaadda22dfab4b0a428733d5489&v=4
url: https://github.com/oliverxchen
- login: jaredtrog
avatarUrl: https://avatars.githubusercontent.com/u/4381365?v=4
url: https://github.com/jaredtrog
- login: dudikbender
avatarUrl: https://avatars.githubusercontent.com/u/53487583?u=3a57542938ebfd57579a0111db2b297e606d9681&v=4
url: https://github.com/dudikbender
- login: roboflow
avatarUrl: https://avatars.githubusercontent.com/u/53104118?v=4
url: https://github.com/roboflow
- login: Ryandaydev
avatarUrl: https://avatars.githubusercontent.com/u/4292423?u=679ff84cb7b988c5795a5fa583857f574a055763&v=4
avatarUrl: https://avatars.githubusercontent.com/u/4292423?u=87b1afc7f4fff933779959270e2d168339e91402&v=4
url: https://github.com/Ryandaydev
- login: gorhack
avatarUrl: https://avatars.githubusercontent.com/u/4141690?u=ec119ebc4bdf00a7bc84657a71aa17834f4f27f3&v=4
@@ -149,9 +140,6 @@ sponsors:
- login: mj0331
avatarUrl: https://avatars.githubusercontent.com/u/3890353?u=1c627ac1a024515b4871de5c3ebbfaa1a57f65d4&v=4
url: https://github.com/mj0331
- login: anomaly
avatarUrl: https://avatars.githubusercontent.com/u/3654837?v=4
url: https://github.com/anomaly
- login: aacayaco
avatarUrl: https://avatars.githubusercontent.com/u/3634801?u=eaadda178c964178fcb64886f6c732172c8f8219&v=4
url: https://github.com/aacayaco
@@ -167,36 +155,24 @@ sponsors:
- login: knallgelb
avatarUrl: https://avatars.githubusercontent.com/u/2358812?u=c48cb6362b309d74cbf144bd6ad3aed3eb443e82&v=4
url: https://github.com/knallgelb
- login: keimos
avatarUrl: https://avatars.githubusercontent.com/u/1723255?u=fb87c72da55f72da6618aa94c8f3791ebab6b68a&v=4
url: https://github.com/keimos
- login: dodo5522
avatarUrl: https://avatars.githubusercontent.com/u/1362607?u=9bf1e0e520cccc547c046610c468ce6115bbcf9f&v=4
url: https://github.com/dodo5522
- login: mintuhouse
avatarUrl: https://avatars.githubusercontent.com/u/769950?u=ecfbd79a97d33177e0d093ddb088283cf7fe8444&v=4
url: https://github.com/mintuhouse
- login: falkben
avatarUrl: https://avatars.githubusercontent.com/u/653031?u=ad9838e089058c9e5a0bab94c0eec7cc181e0cd0&v=4
url: https://github.com/falkben
- login: netsatan
avatarUrl: https://avatars.githubusercontent.com/u/955557?u=cb8fc0ae7f7b06807f0a58e335b1af96c9da0344&v=4
url: https://github.com/netsatan
- login: koxudaxi
avatarUrl: https://avatars.githubusercontent.com/u/630670?u=507d8577b4b3670546b449c4c2ccbc5af40d72f7&v=4
url: https://github.com/koxudaxi
- login: wshayes
avatarUrl: https://avatars.githubusercontent.com/u/365303?u=07ca03c5ee811eb0920e633cc3c3db73dbec1aa5&v=4
url: https://github.com/wshayes
- login: pamelafox
avatarUrl: https://avatars.githubusercontent.com/u/297042?v=4
url: https://github.com/pamelafox
- login: robintw
avatarUrl: https://avatars.githubusercontent.com/u/296686?v=4
url: https://github.com/robintw
- login: jstanden
avatarUrl: https://avatars.githubusercontent.com/u/63288?u=c3658d57d2862c607a0e19c2101c3c51876e36ad&v=4
url: https://github.com/jstanden
- login: RaamEEIL
avatarUrl: https://avatars.githubusercontent.com/u/20320552?v=4
url: https://github.com/RaamEEIL
- login: ashi-agrawal
avatarUrl: https://avatars.githubusercontent.com/u/17105294?u=99c7a854035e5398d8e7b674f2d42baae6c957f8&v=4
url: https://github.com/ashi-agrawal
- login: mjohnsey
avatarUrl: https://avatars.githubusercontent.com/u/16784016?u=38fad2e6b411244560b3af99c5f5a4751bc81865&v=4
url: https://github.com/mjohnsey
@@ -221,63 +197,57 @@ sponsors:
- login: FernandoCelmer
avatarUrl: https://avatars.githubusercontent.com/u/6262214?u=58ba6d5888fa7f355934e52db19f950e20b38162&v=4
url: https://github.com/FernandoCelmer
- login: geodata-no
avatarUrl: https://avatars.githubusercontent.com/u/5946299?v=4
url: https://github.com/geodata-no
- login: eseglem
avatarUrl: https://avatars.githubusercontent.com/u/5920492?u=208d419cf667b8ac594c82a8db01932c7e50d057&v=4
url: https://github.com/eseglem
- login: ternaus
avatarUrl: https://avatars.githubusercontent.com/u/5481618?u=513a26b02a39e7a28d587cd37c6cc877ea368e6e&v=4
url: https://github.com/ternaus
- - login: Artur-Galstyan
avatarUrl: https://avatars.githubusercontent.com/u/63471891?u=e8691f386037e51a737cc0ba866cd8c89e5cf109&v=4
url: https://github.com/Artur-Galstyan
- login: manoelpqueiroz
- - login: jpfyoder
avatarUrl: https://avatars.githubusercontent.com/u/7548821?u=1683290ed65dae6987d673da044067577ee71521&v=4
url: https://github.com/jpfyoder
- - login: manoelpqueiroz
avatarUrl: https://avatars.githubusercontent.com/u/23669137?u=b12e84b28a84369ab5b30bd5a79e5788df5a0756&v=4
url: https://github.com/manoelpqueiroz
- login: Artur-Galstyan
avatarUrl: https://avatars.githubusercontent.com/u/63471891?u=e8691f386037e51a737cc0ba866cd8c89e5cf109&v=4
url: https://github.com/Artur-Galstyan
- - login: pawamoy
avatarUrl: https://avatars.githubusercontent.com/u/3999221?u=b030e4c89df2f3a36bc4710b925bdeb6745c9856&v=4
url: https://github.com/pawamoy
- login: siavashyj
avatarUrl: https://avatars.githubusercontent.com/u/43583410?u=562005ddc7901cd27a1219a118a2363817b14977&v=4
url: https://github.com/siavashyj
- login: caviri
avatarUrl: https://avatars.githubusercontent.com/u/45425937?u=cab1bb03a0326fe45b2363866b1d78c9bc8055d8&v=4
url: https://github.com/caviri
- login: mobyw
avatarUrl: https://avatars.githubusercontent.com/u/44370805?v=4
url: https://github.com/mobyw
- login: ArtyomVancyan
avatarUrl: https://avatars.githubusercontent.com/u/44609997?v=4
url: https://github.com/ArtyomVancyan
- login: caviri
avatarUrl: https://avatars.githubusercontent.com/u/45425937?u=5f3d66ea5edea94c028c51ebf1c0f3b37e6c3db5&v=4
url: https://github.com/caviri
- login: hgalytoby
avatarUrl: https://avatars.githubusercontent.com/u/50397689?u=6cc9028f3db63f8f60ad21c17b1ce4b88c4e2e60&v=4
url: https://github.com/hgalytoby
- login: johnl28
avatarUrl: https://avatars.githubusercontent.com/u/54412955?u=47dd06082d1c39caa90c752eb55566e4f3813957&v=4
url: https://github.com/johnl28
- login: danielunderwood
avatarUrl: https://avatars.githubusercontent.com/u/4472301?v=4
url: https://github.com/danielunderwood
- login: hoenie-ams
avatarUrl: https://avatars.githubusercontent.com/u/25708487?u=cda07434f0509ac728d9edf5e681117c0f6b818b&v=4
url: https://github.com/hoenie-ams
- login: joerambo
avatarUrl: https://avatars.githubusercontent.com/u/26282974?v=4
url: https://github.com/joerambo
- login: engineerjoe440
avatarUrl: https://avatars.githubusercontent.com/u/33275230?u=eb223cad27017bb1e936ee9b429b450d092d0236&v=4
url: https://github.com/engineerjoe440
- login: bnkc
avatarUrl: https://avatars.githubusercontent.com/u/34930566?u=4771ac4e64066f0847d40e5b29910adabd9b2372&v=4
url: https://github.com/bnkc
- login: siavashyj
avatarUrl: https://avatars.githubusercontent.com/u/43583410?u=562005ddc7901cd27a1219a118a2363817b14977&v=4
url: https://github.com/siavashyj
- login: petercool
avatarUrl: https://avatars.githubusercontent.com/u/37613029?u=75aa8c6729e6e8f85a300561c4dbeef9d65c8797&v=4
url: https://github.com/petercool
- login: PelicanQ
avatarUrl: https://avatars.githubusercontent.com/u/77930606?v=4
url: https://github.com/PelicanQ
- login: PunRabbit
avatarUrl: https://avatars.githubusercontent.com/u/70463212?u=1a835cfbc99295a60c8282f6aa6199d1b42241a5&v=4
url: https://github.com/PunRabbit
- login: bnkc
avatarUrl: https://avatars.githubusercontent.com/u/34930566?u=888af82706afa36727feebce0e62225905926131&v=4
url: https://github.com/bnkc
- login: joerambo
avatarUrl: https://avatars.githubusercontent.com/u/26282974?v=4
url: https://github.com/joerambo
- login: hoenie-ams
avatarUrl: https://avatars.githubusercontent.com/u/25708487?u=cda07434f0509ac728d9edf5e681117c0f6b818b&v=4
url: https://github.com/hoenie-ams
- login: nisutec
avatarUrl: https://avatars.githubusercontent.com/u/25281462?u=e562484c451fdfc59053163f64405f8eb262b8b0&v=4
url: https://github.com/nisutec
- login: joshuatz
avatarUrl: https://avatars.githubusercontent.com/u/17817563?u=f1bf05b690d1fc164218f0b420cdd3acb7913e21&v=4
url: https://github.com/joshuatz
- login: hgalytoby
avatarUrl: https://avatars.githubusercontent.com/u/50397689?u=6cc9028f3db63f8f60ad21c17b1ce4b88c4e2e60&v=4
url: https://github.com/hgalytoby
- login: my3
avatarUrl: https://avatars.githubusercontent.com/u/1825270?v=4
url: https://github.com/my3
@@ -290,6 +260,9 @@ sponsors:
- login: tochikuji
avatarUrl: https://avatars.githubusercontent.com/u/851759?v=4
url: https://github.com/tochikuji
- login: falkben
avatarUrl: https://avatars.githubusercontent.com/u/653031?u=ad9838e089058c9e5a0bab94c0eec7cc181e0cd0&v=4
url: https://github.com/falkben
- login: ceb10n
avatarUrl: https://avatars.githubusercontent.com/u/235213?u=edcce471814a1eba9f0cdaa4cd0de18921a940a6&v=4
url: https://github.com/ceb10n
@@ -302,24 +275,12 @@ sponsors:
- login: ddanier
avatarUrl: https://avatars.githubusercontent.com/u/113563?u=ed1dc79de72f93bd78581f88ebc6952b62f472da&v=4
url: https://github.com/ddanier
- login: nisutec
avatarUrl: https://avatars.githubusercontent.com/u/25281462?u=e562484c451fdfc59053163f64405f8eb262b8b0&v=4
url: https://github.com/nisutec
- login: joshuatz
avatarUrl: https://avatars.githubusercontent.com/u/17817563?u=f1bf05b690d1fc164218f0b420cdd3acb7913e21&v=4
url: https://github.com/joshuatz
- login: TheR1D
avatarUrl: https://avatars.githubusercontent.com/u/16740832?u=b0dfdbdb27b79729430c71c6128962f77b7b53f7&v=4
url: https://github.com/TheR1D
- login: Zuzah
avatarUrl: https://avatars.githubusercontent.com/u/10934846?u=1ef43e075ddc87bd1178372bf4d95ee6175cae27&v=4
url: https://github.com/Zuzah
- login: mntolia
avatarUrl: https://avatars.githubusercontent.com/u/10390224?v=4
url: https://github.com/mntolia
- login: hard-coders
avatarUrl: https://avatars.githubusercontent.com/u/9651103?u=78d12d1acdf853c817700145e73de7fd9e5d068b&v=4
url: https://github.com/hard-coders
- login: DMantis
avatarUrl: https://avatars.githubusercontent.com/u/9536869?u=652dd0d49717803c0cbcbf44f7740e53cf2d4892&v=4
url: https://github.com/DMantis
@@ -332,9 +293,6 @@ sponsors:
- login: harsh183
avatarUrl: https://avatars.githubusercontent.com/u/7780198?v=4
url: https://github.com/harsh183
- login: katnoria
avatarUrl: https://avatars.githubusercontent.com/u/7674948?u=09767eb13e07e09496c5fee4e5ce21d9eac34a56&v=4
url: https://github.com/katnoria
- login: KentShikama
avatarUrl: https://avatars.githubusercontent.com/u/6329898?u=8b236810db9b96333230430837e1f021f9246da1&v=4
url: https://github.com/KentShikama
@@ -347,33 +305,33 @@ sponsors:
- login: rangulvers
avatarUrl: https://avatars.githubusercontent.com/u/5235430?u=e254d4af4ace5a05fa58372ae677c7d26f0d5a53&v=4
url: https://github.com/rangulvers
- - login: KOZ39
avatarUrl: https://avatars.githubusercontent.com/u/38822500?u=9dfc0a697df1c9628f08e20dc3fb17b1afc4e5a7&v=4
url: https://github.com/KOZ39
- login: danielunderwood
avatarUrl: https://avatars.githubusercontent.com/u/4472301?v=4
url: https://github.com/danielunderwood
- - login: morzan1001
avatarUrl: https://avatars.githubusercontent.com/u/47593005?u=c30ab7230f82a12a9b938dcb54f84a996931409a&v=4
url: https://github.com/morzan1001
- login: ArtyomVancyan
avatarUrl: https://avatars.githubusercontent.com/u/44609997?v=4
url: https://github.com/ArtyomVancyan
- login: rwxd
avatarUrl: https://avatars.githubusercontent.com/u/40308458?u=cd04a39e3655923be4f25c2ba8a5a07b3da3230a&v=4
url: https://github.com/rwxd
- login: morzan1001
avatarUrl: https://avatars.githubusercontent.com/u/47593005?u=c30ab7230f82a12a9b938dcb54f84a996931409a&v=4
url: https://github.com/morzan1001
- login: Olegt0rr
avatarUrl: https://avatars.githubusercontent.com/u/25399456?u=3e87b5239a2f4600975ba13be73054f8567c6060&v=4
url: https://github.com/Olegt0rr
- login: larsyngvelundin
avatarUrl: https://avatars.githubusercontent.com/u/34173819?u=74958599695bf83ac9f1addd935a51548a10c6b0&v=4
url: https://github.com/larsyngvelundin
- login: KOZ39
avatarUrl: https://avatars.githubusercontent.com/u/38822500?u=9dfc0a697df1c9628f08e20dc3fb17b1afc4e5a7&v=4
url: https://github.com/KOZ39
- login: andrecorumba
avatarUrl: https://avatars.githubusercontent.com/u/37807517?u=9b9be3b41da9bda60957da9ef37b50dbf65baa61&v=4
url: https://github.com/andrecorumba
- login: Olegt0rr
avatarUrl: https://avatars.githubusercontent.com/u/25399456?u=3e87b5239a2f4600975ba13be73054f8567c6060&v=4
url: https://github.com/Olegt0rr
- login: diogotoporcov
avatarUrl: https://avatars.githubusercontent.com/u/207575398?u=1fa7cf41b4181faa4d27f38bc37a374c17b5163b&v=4
url: https://github.com/diogotoporcov
- login: CoderDeltaLAN
avatarUrl: https://avatars.githubusercontent.com/u/152043745?u=4ff541efffb7d134e60c5fcf2dd1e343f90bb782&v=4
avatarUrl: https://avatars.githubusercontent.com/u/152043745?u=8d60fb2d40206045a0b87c65cc54d1654da63f93&v=4
url: https://github.com/CoderDeltaLAN
- login: hippoley
avatarUrl: https://avatars.githubusercontent.com/u/135493401?u=1164ef48a645a7c12664fabc1638fbb7e1c459b0&v=4
url: https://github.com/hippoley
- login: nayasinghania
avatarUrl: https://avatars.githubusercontent.com/u/74111380?u=752e99a5e139389fdc0a0677122adc08438eb076&v=4
url: https://github.com/nayasinghania
- login: onestn
avatarUrl: https://avatars.githubusercontent.com/u/62360849?u=746dd21c34e7e06eefb11b03e8bb01aaae3c2a4f&v=4
url: https://github.com/onestn
@@ -383,6 +341,12 @@ sponsors:
- login: andreagrandi
avatarUrl: https://avatars.githubusercontent.com/u/636391?u=13d90cb8ec313593a5b71fbd4e33b78d6da736f5&v=4
url: https://github.com/andreagrandi
- login: 0xsummerday
avatarUrl: https://avatars.githubusercontent.com/u/13888940?u=d0d45d5e2d7efd880a32d030fb222a51406c986f&v=4
url: https://github.com/0xsummerday
- login: DaxServer
avatarUrl: https://avatars.githubusercontent.com/u/7479937?u=cf2f97f958e47b209679d6aa2ad8723ef0d1cd0f&v=4
url: https://github.com/DaxServer
- login: msserpa
avatarUrl: https://avatars.githubusercontent.com/u/6334934?u=82c4489eb1559d88d2990d60001901b14f722bbb&v=4
url: https://github.com/msserpa
+9 -9
View File
@@ -2,21 +2,21 @@ members:
- login: tiangolo
avatar_url: https://avatars.githubusercontent.com/u/1326112
url: https://github.com/tiangolo
- login: Kludex
avatar_url: https://avatars.githubusercontent.com/u/7353520
url: https://github.com/Kludex
- login: alejsdev
avatar_url: https://avatars.githubusercontent.com/u/90076947
url: https://github.com/alejsdev
- login: svlandeg
avatar_url: https://avatars.githubusercontent.com/u/8796347
url: https://github.com/svlandeg
- login: YuriiMotov
avatar_url: https://avatars.githubusercontent.com/u/109919500
url: https://github.com/YuriiMotov
- login: svlandeg
avatar_url: https://avatars.githubusercontent.com/u/8796347
url: https://github.com/svlandeg
- login: alejsdev
avatar_url: https://avatars.githubusercontent.com/u/90076947
url: https://github.com/alejsdev
- login: patrick91
avatar_url: https://avatars.githubusercontent.com/u/667029
url: https://github.com/patrick91
- login: luzzodev
avatar_url: https://avatars.githubusercontent.com/u/27291415
url: https://github.com/luzzodev
- login: Kludex
avatar_url: https://avatars.githubusercontent.com/u/7353520
url: https://github.com/Kludex
+1 -1
View File
@@ -61,6 +61,6 @@ bronze:
# - url: https://testdriven.io/courses/tdd-fastapi/
# title: Learn to build high-quality web apps with best practices
# img: /img/sponsors/testdriven.svg
- url: https://www.testmu.ai/?utm_source=fastapi&utm_medium=partner&utm_campaign=sponsor&utm_term=opensource&utm_content=webpage
- url: https://www.testmuai.com/?utm_source=fastapi&utm_medium=partner&utm_campaign=sponsor&utm_term=opensource&utm_content=webpage
title: TestMu AI. The Native AI-Agentic Cloud Platform to Supercharge Quality Engineering.
img: /img/sponsors/testmu.png
@@ -243,5 +243,5 @@ For example:
To see what exactly you can include in the responses, you can check these sections in the OpenAPI specification:
* [OpenAPI Responses Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#responses-object), it includes the `Response Object`.
* [OpenAPI Response Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#response-object), you can include anything from this directly in each response inside your `responses` parameter. Including `description`, `headers`, `content` (inside of this is that you declare different media types and JSON Schemas), and `links`.
* [OpenAPI Responses Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#responses-object), it includes the `Response Object`.
* [OpenAPI Response Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#response-object), you can include anything from this directly in each response inside your `responses` parameter. Including `description`, `headers`, `content` (inside of this is that you declare different media types and JSON Schemas), and `links`.
+1 -1
View File
@@ -45,7 +45,7 @@ You can run your tests as usual via:
<div class="termy">
```console
$ pytest
$ uv run pytest
---> 100%
```
+5 -5
View File
@@ -33,7 +33,7 @@ If your **server** is behind a trusted **proxy** and only the proxy talks to it,
<div class="termy">
```console
$ fastapi run --forwarded-allow-ips="*"
$ uv run fastapi run --forwarded-allow-ips="*"
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
@@ -170,7 +170,7 @@ To achieve this, you can use the command line option `--root-path` like:
<div class="termy">
```console
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
$ uv run fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
@@ -200,7 +200,7 @@ Then, if you start Uvicorn with:
<div class="termy">
```console
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
$ uv run fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
@@ -253,7 +253,7 @@ In a case like that (without a stripped path prefix), the proxy would listen on
You can easily run the experiment locally with a stripped path prefix using [Traefik](https://docs.traefik.io/).
[Download Traefik](https://github.com/containous/traefik/releases), it's a single binary, you can extract the compressed file and run it directly from the terminal.
[Download Traefik](https://github.com/traefik/traefik/releases), it's a single binary, you can extract the compressed file and run it directly from the terminal.
Then create a file `traefik.toml` with:
@@ -321,7 +321,7 @@ And now start your app, using the `--root-path` option:
<div class="termy">
```console
$ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
$ uv run fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
+2 -2
View File
@@ -6,7 +6,7 @@ But FastAPI also supports using [`dataclasses`](https://docs.python.org/3/librar
{* ../../docs_src/dataclasses_/tutorial001_py310.py hl[1,6:11,18:19] *}
This is still supported thanks to **Pydantic**, as it has [internal support for `dataclasses`](https://docs.pydantic.dev/latest/concepts/dataclasses/#use-of-stdlib-dataclasses-with-basemodel).
This is still supported thanks to **Pydantic**, as it has [internal support for `dataclasses`](https://pydantic.dev/docs/validation/latest/concepts/dataclasses/#usage-of-stdlib-dataclasses-with-basemodel).
So, even with the code above that doesn't use Pydantic explicitly, FastAPI is using Pydantic to convert those standard dataclasses to Pydantic's own flavor of dataclasses.
@@ -88,7 +88,7 @@ Check the in-code annotation tips above to see more specific details.
You can also combine `dataclasses` with other Pydantic models, inherit from them, include them in your own models, etc.
To learn more, check the [Pydantic docs about dataclasses](https://docs.pydantic.dev/latest/concepts/dataclasses/).
To learn more, check the [Pydantic docs about dataclasses](https://pydantic.dev/docs/validation/latest/concepts/dataclasses/).
## Version { #version }
+1 -1
View File
@@ -154,7 +154,7 @@ Underneath, in the ASGI technical specification, this is part of the [Lifespan P
/// note
You can read more about the Starlette `lifespan` handlers in [Starlette's Lifespan' docs](https://www.starlette.dev/lifespan/).
You can read more about the Starlette `lifespan` handlers in [Starlette's Lifespan' docs](https://starlette.dev/lifespan/).
Including how to handle lifespan state that can be used in other areas of your code.
+1 -1
View File
@@ -12,7 +12,7 @@ A versatile option is the [OpenAPI Generator](https://openapi-generator.tech/),
For **TypeScript clients**, [Hey API](https://heyapi.dev/) is a purpose-built solution, providing an optimized experience for the TypeScript ecosystem.
You can discover more SDK generators on [OpenAPI.Tools](https://openapi.tools/#sdk).
You can discover more SDK generators on [OpenAPI.Tools](https://openapi.tools/categories/sdk-generators).
/// tip
+2 -2
View File
@@ -91,7 +91,7 @@ There are many other ASGI middlewares.
For example:
* [Uvicorn's `ProxyHeadersMiddleware`](https://github.com/encode/uvicorn/blob/master/uvicorn/middleware/proxy_headers.py)
* [Uvicorn's `ProxyHeadersMiddleware`](https://github.com/Kludex/uvicorn/blob/main/uvicorn/middleware/proxy_headers.py)
* [MessagePack](https://github.com/florimondmanca/msgpack-asgi)
To see other available middlewares check [Starlette's Middleware docs](https://www.starlette.dev/middleware/) and the [ASGI Awesome List](https://github.com/florimondmanca/awesome-asgi).
To see other available middlewares check [Starlette's Middleware docs](https://starlette.dev/middleware/) and the [ASGI Awesome List](https://github.com/florimondmanca/awesome-asgi).
+3 -3
View File
@@ -35,7 +35,7 @@ This part is pretty normal, most of the code is probably already familiar to you
/// tip
The `callback_url` query parameter uses a Pydantic [Url](https://docs.pydantic.dev/latest/api/networks/) type.
The `callback_url` query parameter uses a Pydantic [Url](https://pydantic.dev/docs/validation/latest/api/pydantic/networks/) type.
///
@@ -106,11 +106,11 @@ It should look just like a normal FastAPI *path operation*:
There are 2 main differences from a normal *path operation*:
* It doesn't need to have any actual code, because your app will never call this code. It's only used to document the *external API*. So, the function could just have `pass`.
* The *path* can contain an [OpenAPI 3 expression](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression) (see more below) where it can use variables with parameters and parts of the original request sent to *your API*.
* The *path* can contain an [OpenAPI 3 expression](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#key-expression) (see more below) where it can use variables with parameters and parts of the original request sent to *your API*.
### The callback path expression { #the-callback-path-expression }
The callback *path* can have an [OpenAPI 3 expression](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md#key-expression) that can contain parts of the original request sent to *your API*.
The callback *path* can have an [OpenAPI 3 expression](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#key-expression) that can contain parts of the original request sent to *your API*.
In this case, it's the `str`:
+1 -1
View File
@@ -48,4 +48,4 @@ And as the `Response` can be used frequently to set headers and cookies, **FastA
///
To see all the available parameters and options, check the [documentation in Starlette](https://www.starlette.dev/responses/#set-cookie).
To see all the available parameters and options, check the [documentation in Starlette](https://starlette.dev/responses/#set-cookie).
+1 -1
View File
@@ -38,4 +38,4 @@ And as the `Response` can be used frequently to set headers and cookies, **FastA
Keep in mind that custom proprietary headers can be added [using the `X-` prefix](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers).
But if you have custom headers that you want a client in a browser to be able to see, you need to add them to your CORS configurations (read more in [CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md)), using the parameter `expose_headers` documented in [Starlette's CORS docs](https://www.starlette.dev/middleware/#corsmiddleware).
But if you have custom headers that you want a client in a browser to be able to see, you need to add them to your CORS configurations (read more in [CORS (Cross-Origin Resource Sharing)](../tutorial/cors.md)), using the parameter `expose_headers` documented in [Starlette's CORS docs](https://starlette.dev/middleware/#corsmiddleware).
+34 -10
View File
@@ -6,9 +6,13 @@ Most of these settings are variable (can change), like database URLs. And many c
For this reason it's common to provide them in environment variables that are read by the application.
An **environment variable** (also known as an **env var**) is a value that lives outside of the Python code, in the operating system, and can be read by your application and other programs.
You can create an environment variable for a command when you run it. You will see the platform-specific commands below.
/// tip
To understand environment variables you can read [Environment Variables](../environment-variables.md).
Read the [Environment Variables guide](https://tiangolo.com/guides/environment-variables/) for a detailed explanation of how environment variables work.
///
@@ -20,16 +24,16 @@ That means that any value read in Python from an environment variable will be a
## Pydantic `Settings` { #pydantic-settings }
Fortunately, Pydantic provides a great utility to handle these settings coming from environment variables with [Pydantic: Settings management](https://docs.pydantic.dev/latest/concepts/pydantic_settings/).
Fortunately, Pydantic provides a great utility to handle these settings coming from environment variables with [Pydantic: Settings management](https://pydantic.dev/docs/validation/latest/concepts/pydantic_settings/).
### Install `pydantic-settings` { #install-pydantic-settings }
First, make sure you create your [virtual environment](../virtual-environments.md), activate it, and then install the `pydantic-settings` package:
Add the `pydantic-settings` package to your project:
<div class="termy">
```console
$ pip install pydantic-settings
$ uv add pydantic-settings
---> 100%
```
@@ -40,7 +44,7 @@ It also comes included when you install the `all` extras with:
<div class="termy">
```console
$ pip install "fastapi[all]"
$ uv add "fastapi[all]"
---> 100%
```
@@ -76,19 +80,39 @@ Then you can use the new `settings` object in your application:
Next, you would run the server passing the configurations as environment variables, for example you could set an `ADMIN_EMAIL` and `APP_NAME` with:
//// tab | Linux, macOS, Windows Bash
<div class="termy">
```console
$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" fastapi run main.py
$ ADMIN_EMAIL="deadpool@example.com" APP_NAME="ChimichangApp" uv run fastapi run main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```console
$ $Env:ADMIN_EMAIL = "deadpool@example.com"
$ $Env:APP_NAME = "ChimichangApp"
$ uv run fastapi run main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
</div>
////
/// tip
To set multiple env vars for a single command just separate them with a space, and put them all before the command.
In Bash, to set multiple env vars for a single command, separate them with a space and put them all before the command.
///
@@ -172,11 +196,11 @@ But a dotenv file doesn't really have to have that exact filename.
///
Pydantic has support for reading from these types of files using an external library. You can read more at [Pydantic Settings: Dotenv (.env) support](https://docs.pydantic.dev/latest/concepts/pydantic_settings/#dotenv-env-support).
Pydantic has support for reading from these types of files using an external library. You can read more at [Pydantic Settings: Dotenv (.env) support](https://pydantic.dev/docs/validation/latest/concepts/pydantic_settings/#dotenv-env-support).
/// tip
For this to work, you need to `pip install python-dotenv`.
For this to work, add `python-dotenv` to your project with `uv add python-dotenv`.
///
@@ -197,7 +221,7 @@ And then update your `config.py` with:
/// tip
The `model_config` attribute is used just for Pydantic configuration. You can read more at [Pydantic: Concepts: Configuration](https://docs.pydantic.dev/latest/concepts/config/).
The `model_config` attribute is used just for Pydantic configuration. You can read more at [Pydantic: Concepts: Configuration](https://pydantic.dev/docs/validation/latest/concepts/config/).
///
+1 -1
View File
@@ -35,7 +35,7 @@ Now, run the `fastapi` command:
<div class="termy">
```console
$ fastapi dev
$ uv run fastapi dev
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
+3 -3
View File
@@ -8,12 +8,12 @@ There are utilities to configure it easily that you can use directly in your **F
## Install dependencies { #install-dependencies }
Make sure you create a [virtual environment](../virtual-environments.md), activate it, and install `jinja2`:
Add `jinja2` to your project:
<div class="termy">
```console
$ pip install jinja2
$ uv add jinja2
---> 100%
```
@@ -123,4 +123,4 @@ And because you are using `StaticFiles`, that CSS file would be served automatic
## More details { #more-details }
For more details, including how to test templates, check [Starlette's docs on templates](https://www.starlette.dev/templates/).
For more details, including how to test templates, check [Starlette's docs on templates](https://starlette.dev/templates/).
+1 -1
View File
@@ -5,7 +5,7 @@ When you need `lifespan` to run in your tests, you can use the `TestClient` with
{* ../../docs_src/app_testing/tutorial004_py310.py hl[9:15,18,27:28,30:32,41:43] *}
You can read more details about the ["Running lifespan in tests in the official Starlette documentation site."](https://www.starlette.dev/lifespan/#running-lifespan-in-tests)
You can read more details about the ["Running lifespan in tests in the official Starlette documentation site."](https://starlette.dev/lifespan/#running-lifespan-in-tests)
For the deprecated `startup` and `shutdown` events, you can use the `TestClient` as follows:
+1 -1
View File
@@ -8,6 +8,6 @@ For this, you use the `TestClient` in a `with` statement, connecting to the WebS
/// note
For more details, check Starlette's documentation for [testing WebSockets](https://www.starlette.dev/testclient/#testing-websocket-sessions).
For more details, check Starlette's documentation for [testing WebSockets](https://starlette.dev/testclient/#testing-websocket-sessions).
///
@@ -15,7 +15,7 @@ But there are situations where you might need to access the `Request` object dir
## Details about the `Request` object { #details-about-the-request-object }
As **FastAPI** is actually **Starlette** underneath, with a layer of several tools on top, you can use Starlette's [`Request`](https://www.starlette.dev/requests/) object directly when you need to.
As **FastAPI** is actually **Starlette** underneath, with a layer of several tools on top, you can use Starlette's [`Request`](https://starlette.dev/requests/) object directly when you need to.
It would also mean that if you get data from the `Request` object directly (for example, read the body) it won't be validated, converted or documented (with OpenAPI, for the automatic API user interface) by FastAPI.
@@ -45,7 +45,7 @@ The same way, you can declare any other parameter as normally, and additionally,
## `Request` documentation { #request-documentation }
You can read more details about the [`Request` object in the official Starlette documentation site](https://www.starlette.dev/requests/).
You can read more details about the [`Request` object in the official Starlette documentation site](https://starlette.dev/requests/).
/// note | Technical Details
+6 -6
View File
@@ -4,12 +4,12 @@ You can use [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSoc
## Install `websockets` { #install-websockets }
Make sure you create a [virtual environment](../virtual-environments.md), activate it, and install `websockets` (a Python library that makes it easy to use the "WebSocket" protocol):
Add `websockets` (a Python library that makes it easy to use the "WebSocket" protocol) to your project:
<div class="termy">
```console
$ pip install websockets
$ uv add websockets
---> 100%
```
@@ -69,7 +69,7 @@ Put your code in a file `main.py` and then run your application:
<div class="termy">
```console
$ fastapi dev
$ uv run fastapi dev
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
@@ -126,7 +126,7 @@ Run your application:
<div class="termy">
```console
$ fastapi dev
$ uv run fastapi dev
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
@@ -182,5 +182,5 @@ If you need something easy to integrate with FastAPI but that is more robust, su
To learn more about the options, check Starlette's documentation for:
* [The `WebSocket` class](https://www.starlette.dev/websockets/).
* [Class-based WebSocket handling](https://www.starlette.dev/endpoints/#websocketendpoint).
* [The `WebSocket` class](https://starlette.dev/websockets/).
* [Class-based WebSocket handling](https://starlette.dev/endpoints/#websocketendpoint).
+1 -1
View File
@@ -8,7 +8,7 @@ For that, you can use the `WSGIMiddleware` and use it to wrap your WSGI applicat
/// note
This requires installing `a2wsgi` for example with `pip install a2wsgi`.
This requires adding `a2wsgi` to your project, for example with `uv add a2wsgi`.
///
+6 -6
View File
@@ -125,7 +125,7 @@ Adopt and use an open standard for API specifications, instead of a custom schem
And integrate standards-based user interface tools:
* [Swagger UI](https://github.com/swagger-api/swagger-ui)
* [ReDoc](https://github.com/Rebilly/ReDoc)
* [ReDoc](https://github.com/Redocly/redoc)
These two were chosen for being fairly popular and stable, but doing a quick search, you could find dozens of alternative user interfaces for OpenAPI (that you can use with **FastAPI**).
@@ -237,7 +237,7 @@ Generate the OpenAPI schema automatically, from the same code that defines seria
///
### [NestJS](https://nestjs.com/) (and [Angular](https://angular.io/)) { #nestjs-and-angular }
### [NestJS](https://nestjs.com/) (and [Angular](https://angular.dev/)) { #nestjs-and-angular }
This isn't even Python, NestJS is a JavaScript (TypeScript) NodeJS framework inspired by Angular.
@@ -337,7 +337,7 @@ As it is based on the previous standard for synchronous Python web frameworks (W
/// note
Hug was created by Timothy Crosley, the same creator of [`isort`](https://github.com/timothycrosley/isort), a great tool to automatically sort imports in Python files.
Hug was created by Timothy Crosley, the same creator of [`isort`](https://github.com/PyCQA/isort), a great tool to automatically sort imports in Python files.
///
@@ -401,7 +401,7 @@ I consider **FastAPI** a "spiritual successor" to APIStar, while improving and i
## Used by **FastAPI** { #used-by-fastapi }
### [Pydantic](https://docs.pydantic.dev/) { #pydantic }
### [Pydantic](https://pydantic.dev/docs/) { #pydantic }
Pydantic is a library to define data validation, serialization and documentation (using JSON Schema) based on Python type hints.
@@ -417,7 +417,7 @@ Handle all the data validation, data serialization and automatic model documenta
///
### [Starlette](https://www.starlette.dev/) { #starlette }
### [Starlette](https://starlette.dev/) { #starlette }
Starlette is a lightweight <dfn title="The new standard for building asynchronous Python web applications">ASGI</dfn> framework/toolkit, which is ideal for building high-performance asyncio services.
@@ -462,7 +462,7 @@ So, anything that you can do with Starlette, you can do it directly with **FastA
///
### [Uvicorn](https://www.uvicorn.dev/) { #uvicorn }
### [Uvicorn](https://uvicorn.dev) { #uvicorn }
Uvicorn is a lightning-fast ASGI server, built on uvloop and httptools.
+15 -19
View File
@@ -105,36 +105,32 @@ This is what you would want to do in **most cases**, for example:
### Package Requirements { #package-requirements }
You would normally have the **package requirements** for your application in some file.
When you manage your project with `uv`, its direct dependencies are declared in `pyproject.toml` and the exact resolved versions are stored in `uv.lock`.
It would depend mainly on the tool you use to **install** those requirements.
The most common way to do it is to have a file `requirements.txt` with the package names and their versions, one per line.
You would of course use the same ideas you read in [About FastAPI versions](versions.md) to set the ranges of versions.
For example, your `requirements.txt` could look like:
```
fastapi[standard]>=0.113.0,<0.114.0
pydantic>=2.7.0,<3.0.0
```
And you would normally install those package dependencies with `pip`, for example:
You can add the packages your application needs with:
<div class="termy">
```console
$ pip install -r requirements.txt
$ uv add "fastapi[standard]" pydantic
---> 100%
Successfully installed fastapi pydantic
```
</div>
/// note
There are other formats and tools to define and install package dependencies.
The Dockerfile below uses `pip` inside the container. You can export the locked dependencies from your uv project to the `requirements.txt` format it expects:
<div class="termy">
```console
$ uv export --format requirements-txt --no-dev --no-emit-project --output-file requirements.txt
```
</div>
The generated `requirements.txt` is an export for the container build. Continue managing dependencies with `uv add` and regenerate it when `uv.lock` changes.
///
@@ -372,7 +368,7 @@ You will see the automatic interactive API documentation (provided by [Swagger U
And you can also go to [http://192.168.99.100/redoc](http://192.168.99.100/redoc) or [http://127.0.0.1/redoc](http://127.0.0.1/redoc) (or equivalent, using your Docker host).
You will see the alternative automatic documentation (provided by [ReDoc](https://github.com/Rebilly/ReDoc)):
You will see the alternative automatic documentation (provided by [ReDoc](https://github.com/Redocly/redoc)):
![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png)
+1 -1
View File
@@ -5,7 +5,7 @@ You can deploy your FastAPI app to [FastAPI Cloud](https://fastapicloud.com) wit
<div class="termy">
```console
$ fastapi deploy
$ uv run fastapi deploy
Deploying to FastAPI Cloud...
+5 -5
View File
@@ -52,7 +52,7 @@ The main thing you need to run a **FastAPI** application (or any other ASGI appl
There are several alternatives, including:
* [Uvicorn](https://www.uvicorn.dev/): a high performance ASGI server.
* [Uvicorn](https://uvicorn.dev): a high performance ASGI server.
* [Hypercorn](https://hypercorn.readthedocs.io/): an ASGI server compatible with HTTP/2 and Trio among other features.
* [Daphne](https://github.com/django/daphne): the ASGI server built for Django Channels.
* [Granian](https://github.com/emmett-framework/granian): A Rust HTTP server for Python applications.
@@ -73,14 +73,14 @@ When you install FastAPI, it comes with a production server, Uvicorn, and you ca
But you can also install an ASGI server manually.
Make sure you create a [virtual environment](../virtual-environments.md), activate it, and then you can install the server application.
Add the server application to your project.
For example, to install Uvicorn:
<div class="termy">
```console
$ pip install "uvicorn[standard]"
$ uv add "uvicorn[standard]"
---> 100%
```
@@ -95,7 +95,7 @@ By adding the `standard`, Uvicorn will install and use some recommended extra de
That includes `uvloop`, the high-performance drop-in replacement for `asyncio`, that provides the big concurrency performance boost.
When you install FastAPI with something like `pip install "fastapi[standard]"` you already get `uvicorn[standard]` as well.
When you add FastAPI with something like `uv add "fastapi[standard]"` you already get `uvicorn[standard]` as well.
///
@@ -106,7 +106,7 @@ If you installed an ASGI server manually, you would normally need to pass an imp
<div class="termy">
```console
$ uvicorn main:app --host 0.0.0.0 --port 80
$ uv run uvicorn main:app --host 0.0.0.0 --port 80
<span style="color: green;">INFO</span>: Uvicorn running on http://0.0.0.0:80 (Press CTRL+C to quit)
```
+1 -1
View File
@@ -86,7 +86,7 @@ If you prefer to use the `uvicorn` command directly:
<div class="termy">
```console
$ uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4
$ uv run uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4
<font color="#A6E22E">INFO</font>: Uvicorn running on <b>http://0.0.0.0:8080</b> (Press CTRL+C to quit)
<font color="#A6E22E">INFO</font>: Started parent process [<font color="#A1EFE4"><b>27365</b></font>]
<font color="#A6E22E">INFO</font>: Started server process [<font color="#A1EFE4">27368</font>]
+5 -292
View File
@@ -1,298 +1,11 @@
# Environment Variables { #environment-variables }
/// tip
An **environment variable** (also known as an **env var**) is a value that lives outside of your Python code, in the operating system, and can be read by your application and other programs.
If you already know what "environment variables" are and how to use them, feel free to skip this.
FastAPI applications commonly use environment variables for configuration such as database URLs, email credentials, and secret keys.
///
You will learn how to use them for application configuration in [Settings and Environment Variables](advanced/settings.md).
An environment variable (also known as "**env var**") is a variable that lives **outside** of the Python code, in the **operating system**, and could be read by your Python code (or by other programs as well).
## Learn More { #learn-more }
Environment variables could be useful for handling application **settings**, as part of the **installation** of Python, etc.
## Create and Use Env Vars { #create-and-use-env-vars }
You can **create** and use environment variables in the **shell (terminal)**, without needing Python:
//// tab | Linux, macOS, Windows Bash
<div class="termy">
```console
// You could create an env var MY_NAME with
$ export MY_NAME="Wade Wilson"
// Then you could use it with other programs, like
$ echo "Hello $MY_NAME"
Hello Wade Wilson
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```console
// Create an env var MY_NAME
$ $Env:MY_NAME = "Wade Wilson"
// Use it with other programs, like
$ echo "Hello $Env:MY_NAME"
Hello Wade Wilson
```
</div>
////
## Read env vars in Python { #read-env-vars-in-python }
You could also create environment variables **outside** of Python, in the terminal (or with any other method), and then **read them in Python**.
For example you could have a file `main.py` with:
```Python hl_lines="3"
import os
name = os.getenv("MY_NAME", "World")
print(f"Hello {name} from Python")
```
/// tip
The second argument to [`os.getenv()`](https://docs.python.org/3.8/library/os.html#os.getenv) is the default value to return.
If not provided, it's `None` by default, here we provide `"World"` as the default value to use.
///
Then you could call that Python program:
//// tab | Linux, macOS, Windows Bash
<div class="termy">
```console
// Here we don't set the env var yet
$ python main.py
// As we didn't set the env var, we get the default value
Hello World from Python
// But if we create an environment variable first
$ export MY_NAME="Wade Wilson"
// And then call the program again
$ python main.py
// Now it can read the environment variable
Hello Wade Wilson from Python
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```console
// Here we don't set the env var yet
$ python main.py
// As we didn't set the env var, we get the default value
Hello World from Python
// But if we create an environment variable first
$ $Env:MY_NAME = "Wade Wilson"
// And then call the program again
$ python main.py
// Now it can read the environment variable
Hello Wade Wilson from Python
```
</div>
////
As environment variables can be set outside of the code, but can be read by the code, and don't have to be stored (committed to `git`) with the rest of the files, it's common to use them for configurations or **settings**.
You can also create an environment variable only for a **specific program invocation**, that is only available to that program, and only for its duration.
To do that, create it right before the program itself, on the same line:
<div class="termy">
```console
// Create an env var MY_NAME in line for this program call
$ MY_NAME="Wade Wilson" python main.py
// Now it can read the environment variable
Hello Wade Wilson from Python
// The env var no longer exists afterwards
$ python main.py
Hello World from Python
```
</div>
/// tip
You can read more about it at [The Twelve-Factor App: Config](https://12factor.net/config).
///
## Types and Validation { #types-and-validation }
These environment variables can only handle **text strings**, as they are external to Python and have to be compatible with other programs and the rest of the system (and even with different operating systems, such as Linux, Windows, and macOS).
That means that **any value** read in Python from an environment variable **will be a `str`**, and any conversion to a different type or any validation has to be done in code.
You will learn more about using environment variables for handling **application settings** in the [Advanced User Guide - Settings and Environment Variables](./advanced/settings.md).
## `PATH` Environment Variable { #path-environment-variable }
There is a **special** environment variable called **`PATH`** that is used by the operating systems (Linux, macOS, Windows) to find programs to run.
The value of the variable `PATH` is a long string that is made of directories separated by a colon `:` on Linux and macOS, and by a semicolon `;` on Windows.
For example, the `PATH` environment variable could look like this:
//// tab | Linux, macOS
```plaintext
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
```
This means that the system should look for programs in the directories:
* `/usr/local/bin`
* `/usr/bin`
* `/bin`
* `/usr/sbin`
* `/sbin`
////
//// tab | Windows
```plaintext
C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32
```
This means that the system should look for programs in the directories:
* `C:\Program Files\Python312\Scripts`
* `C:\Program Files\Python312`
* `C:\Windows\System32`
////
When you type a **command** in the terminal, the operating system **looks for** the program in **each of those directories** listed in the `PATH` environment variable.
For example, when you type `python` in the terminal, the operating system looks for a program called `python` in the **first directory** in that list.
If it finds it, then it will **use it**. Otherwise it keeps looking in the **other directories**.
### Installing Python and Updating the `PATH` { #installing-python-and-updating-the-path }
When you install Python, you might be asked if you want to update the `PATH` environment variable.
//// tab | Linux, macOS
Let's say you install Python and it ends up in a directory `/opt/custompython/bin`.
If you say yes to update the `PATH` environment variable, then the installer will add `/opt/custompython/bin` to the `PATH` environment variable.
It could look like this:
```plaintext
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin
```
This way, when you type `python` in the terminal, the system will find the Python program in `/opt/custompython/bin` (the last directory) and use that one.
////
//// tab | Windows
Let's say you install Python and it ends up in a directory `C:\opt\custompython\bin`.
If you say yes to update the `PATH` environment variable, then the installer will add `C:\opt\custompython\bin` to the `PATH` environment variable.
```plaintext
C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin
```
This way, when you type `python` in the terminal, the system will find the Python program in `C:\opt\custompython\bin` (the last directory) and use that one.
////
So, if you type:
<div class="termy">
```console
$ python
```
</div>
//// tab | Linux, macOS
The system will **find** the `python` program in `/opt/custompython/bin` and run it.
It would be roughly equivalent to typing:
<div class="termy">
```console
$ /opt/custompython/bin/python
```
</div>
////
//// tab | Windows
The system will **find** the `python` program in `C:\opt\custompython\bin\python` and run it.
It would be roughly equivalent to typing:
<div class="termy">
```console
$ C:\opt\custompython\bin\python
```
</div>
////
This information will be useful when learning about [Virtual Environments](virtual-environments.md).
## Conclusion { #conclusion }
With this you should have a basic understanding of what **environment variables** are and how to use them in Python.
You can also read more about them in the [Wikipedia for Environment Variable](https://en.wikipedia.org/wiki/Environment_variable).
In many cases it's not very obvious how environment variables would be useful and applicable right away. But they keep showing up in many different scenarios when you are developing, so it's good to know about them.
For example, you will need this information in the next section, about [Virtual Environments](virtual-environments.md).
Read the [Environment Variables guide](https://tiangolo.com/guides/environment-variables/) for a detailed, cross-platform explanation, including how to create and read environment variables and how the `PATH` environment variable works.
+4 -4
View File
@@ -2,7 +2,7 @@
**FastAPI <abbr title="command line interface">CLI</abbr>** is a command line program that you can use to serve your FastAPI app, manage your FastAPI project, and more.
When you install FastAPI (e.g. with `pip install "fastapi[standard]"`), it comes with a command line program you can run in the terminal.
When you add FastAPI to your project (e.g. with `uv add "fastapi[standard]"`), it comes with a command line program you can run in the terminal.
To run your FastAPI app for development, you can use the `fastapi dev` command:
@@ -52,7 +52,7 @@ For production you would use `fastapi run` instead of `fastapi dev`. 🚀
///
Internally, **FastAPI CLI** uses [Uvicorn](https://www.uvicorn.dev), a high-performance, production-ready, ASGI server. 😎
Internally, **FastAPI CLI** uses [Uvicorn](https://uvicorn.dev), a high-performance, production-ready, ASGI server. 😎
The `fastapi` CLI will try to detect automatically the FastAPI app to run, assuming it's an object called `app` in a file `main.py` (or a couple other variants).
@@ -100,13 +100,13 @@ from backend.main import app
You can also pass the file path to the `fastapi dev` command, and it will guess the FastAPI app object to use:
```console
$ fastapi dev main.py
$ uv run fastapi dev main.py
```
Or, you can also pass the `--entrypoint` option to the `fastapi dev` command:
```console
$ fastapi dev --entrypoint main:app
$ uv run fastapi dev --entrypoint main:app
```
But you would have to remember to pass the correct path\entrypoint every time you call the `fastapi` command.
+31 -132
View File
@@ -1,7 +1,4 @@
---
hide:
- navigation
include_yaml:
github_sponsors: data/github_sponsors.yml
people: data/people.yml
@@ -33,22 +30,6 @@ This is me:
I'm the creator of **FastAPI**. You can read more about that in [Help FastAPI - Follow the author](help-fastapi.md#follow-the-author).
...But here I want to show you the community.
---
**FastAPI** receives a lot of support from the community. And I want to highlight their contributions.
These are the people that:
* [Help others with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github).
* Create or review Pull Requests.
* Help [manage the repository](https://tiangolo.com/open-source/management-tasks/) (team members).
All these tasks help maintain the repository.
A round of applause to them. 👏 🙇
## Team
This is the current list of team members. 😎
@@ -65,111 +46,17 @@ They have different levels of involvement and permissions, they can perform [rep
</div>
Although the team members have the permissions to perform privileged tasks, all the help from others maintaining FastAPI is very much appreciated! 🙇‍♂️
## FastAPI Experts
These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github). 🙇
For a long time, answering questions from the community in GitHub Discussions was done by other community volunteers.
They have proven to be **FastAPI Experts** by helping many others. ✨
They proved they are **FastAPI Experts** by helping many others. ✨
/// tip
You could become an official FastAPI Expert too!
Just [help others with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github). 🤓
///
You can see the **FastAPI Experts** for:
* [Last Month](#fastapi-experts-last-month) 🤓
* [3 Months](#fastapi-experts-3-months) 😎
* [6 Months](#fastapi-experts-6-months) 🧐
* [1 Year](#fastapi-experts-1-year) 🧑‍🔬
* [**All Time**](#fastapi-experts-all-time) 🧙
### FastAPI Experts - Last Month
These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github) during the last month. 🤓
Here's the hall of fame of the first FastAPI Experts:
<div class="user-list user-list-center">
{% for user in people.last_month_experts[:10] %}
{% if user.login not in skip_users.users %}
<div class="user"><a href="{{ user.url }}"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Questions replied: {{ user.count }}</div></div>
{% endif %}
{% endfor %}
</div>
### FastAPI Experts - 3 Months
These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github) during the last 3 months. 😎
<div class="user-list user-list-center">
{% for user in people.three_months_experts[:10] %}
{% if user.login not in skip_users.users %}
<div class="user"><a href="{{ user.url }}"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Questions replied: {{ user.count }}</div></div>
{% endif %}
{% endfor %}
</div>
### FastAPI Experts - 6 Months
These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github) during the last 6 months. 🧐
<div class="user-list user-list-center">
{% for user in people.six_months_experts[:10] %}
{% if user.login not in skip_users.users %}
<div class="user"><a href="{{ user.url }}"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Questions replied: {{ user.count }}</div></div>
{% endif %}
{% endfor %}
</div>
### FastAPI Experts - 1 Year
These are the users that have been [helping others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github) during the last year. 🧑‍🔬
<div class="user-list user-list-center">
{% for user in people.one_year_experts[:20] %}
{% if user.login not in skip_users.users %}
<div class="user"><a href="{{ user.url }}"><div class="avatar-wrapper"><img src="{{ user.avatarUrl }}"/></div><div class="title">@{{ user.login }}</div></a> <div class="count">Questions replied: {{ user.count }}</div></div>
{% endif %}
{% endfor %}
</div>
### FastAPI Experts - All Time
Here are the all time **FastAPI Experts**. 🤓🤯
These are the users that have [helped others the most with questions in GitHub](help-fastapi.md#help-others-with-questions-in-github) through *all time*. 🧙
<div class="user-list user-list-center">
{% for user in people.experts[:50] %}
{% for user in people.experts[:30] %}
{% if user.login not in skip_users.users %}
@@ -183,15 +70,17 @@ These are the users that have [helped others the most with questions in GitHub](
## Top Contributors
Here are the **Top Contributors**. 👷
Currently, most of the code changes in FastAPI are done by the team.
These users have created the most Pull Requests that have been *merged*.
But over the years, there have also been many contributions made by others.
They have contributed source code, documentation, etc. 📦
They contributed source code, documentation, etc. 📦
Here's the hall of fame of the first **Top Contributors**. 👷
<div class="user-list user-list-center">
{% for user in (contributors.values() | list)[:50] %}
{% for user in (contributors.values() | list)[:30] %}
{% if user.login not in skip_users.users %}
@@ -207,12 +96,14 @@ There are hundreds of other contributors, you can see them all in the [FastAPI G
## Top Translation Reviewers
These users are the **Top Translation Reviewers**. 🕵️
Currently, translations are done using AI tools, steered by the FastAPI team and native speakers.
Translation reviewers have the **power to approve translations** of the documentation. Without them, there wouldn't be documentation in several other languages.
At some point, FastAPI had some documentation pages that community members translated into other languages by hand.
Here's the hall of fame of the first **Top Translation Reviewers**. 🕵️
<div class="user-list user-list-center">
{% for user in (translation_reviewers.values() | list)[:50] %}
{% for user in (translation_reviewers.values() | list)[:30] %}
{% if user.login not in skip_users.users %}
@@ -226,9 +117,7 @@ Translation reviewers have the **power to approve translations** of the document
## Sponsors
These are the **Sponsors**. 😎
They are supporting my work with **FastAPI** (and others), mainly through [GitHub Sponsors](https://github.com/sponsors/tiangolo).
**Sponsors** support **FastAPI** and friends, mainly through [GitHub Sponsors](https://github.com/sponsors/tiangolo).
{% if sponsors %}
@@ -283,12 +172,22 @@ They are supporting my work with **FastAPI** (and others), mainly through [GitHu
## About the data - technical details
The main intention of this page is to highlight the effort of the community to help others.
The main intention of this page has been to highlight the effort of the community to help others, especially efforts that were normally less visible and, in many cases, more arduous, like helping others with questions and reviewing Pull Requests with translations.
Especially including efforts that are normally less visible, and in many cases more arduous, like helping others with questions and reviewing Pull Requests with translations.
It also highlights contributions from sponsors.
The data is calculated each month, you can read the [source code here](https://github.com/fastapi/fastapi/blob/master/scripts/).
The data used to be calculated continuously, each month.
Here I'm also highlighting contributions from sponsors.
As of July 2026, most of the work has been done by (paid) team members for quite some time.
I also reserve the right to update the algorithm, sections, thresholds, etc (just in case 🤷).
GitHub Discussions are answered mostly by team members.
Most of the code changes are done by team members.
And translations are continuously done for the entire documentation in multiple languages, using AI tools, managed by team members.
Additionally, in recent months, there's been an overwhelming amount of AI spam, mainly to cheat the FastAPI Experts system or to get a PR merged by any means and thereby be considered a contributor. You can read more about the point of view in [Automated Code and AI](https://tiangolo.com/open-source/contributing/#automated-code-and-ai).
Because of this, the data for the FastAPI Experts, Top Contributors, and Top Translation Reviewers is no longer continuously updated.
This section is currently kept as a tribute to the humans that helped shape what FastAPI is today. 🙌
+3 -3
View File
@@ -19,7 +19,7 @@ Interactive API documentation and exploration web user interfaces. As the framew
![Swagger UI interaction](https://fastapi.tiangolo.com/img/index/index-03-swagger-02.png)
* Alternative API documentation with [**ReDoc**](https://github.com/Rebilly/ReDoc).
* Alternative API documentation with [**ReDoc**](https://github.com/Redocly/redoc).
![ReDoc](https://fastapi.tiangolo.com/img/index/index-06-redoc-02.png)
@@ -159,7 +159,7 @@ Any integration is designed to be so simple to use (with dependencies) that you
## Starlette features { #starlette-features }
**FastAPI** is fully compatible with (and based on) [**Starlette**](https://www.starlette.dev/). So, any additional Starlette code you have, will also work.
**FastAPI** is fully compatible with (and based on) [**Starlette**](https://starlette.dev/). So, any additional Starlette code you have, will also work.
`FastAPI` is actually a sub-class of `Starlette`. So, if you already know or use Starlette, most of the functionality will work the same way.
@@ -177,7 +177,7 @@ With **FastAPI** you get all of **Starlette**'s features (as FastAPI is just Sta
## Pydantic features { #pydantic-features }
**FastAPI** is fully compatible with (and based on) [**Pydantic**](https://docs.pydantic.dev/). So, any additional Pydantic code you have, will also work.
**FastAPI** is fully compatible with (and based on) [**Pydantic**](https://pydantic.dev/docs/). So, any additional Pydantic code you have, will also work.
Including external libraries also based on Pydantic, such as <abbr title="Object-Relational Mapper">ORM</abbr>s and <abbr title="Object-Document Mapper">ODM</abbr>s for databases.
+7 -15
View File
@@ -45,20 +45,6 @@ You can follow [me (Sebastián Ramírez / `tiangolo`)](https://tiangolo.com), th
* [@tiangolo.com on **Bluesky**](https://bsky.app/profile/tiangolo.com)
* [@tiangolo on **LinkedIn**](https://www.linkedin.com/in/tiangolo/).
## Help others with questions in GitHub { #help-others-with-questions-in-github }
You can try and help others with their questions in [GitHub Discussions](https://github.com/fastapi/fastapi/discussions/categories/questions?discussions_q=category%3AQuestions+is%3Aunanswered).
In many cases you might already know the answer for those questions. 🤓
If you are helping a lot of people with their questions, you will become an official [FastAPI Expert](fastapi-people.md#fastapi-experts). 🎉
Just remember, the most important point is: try to be kind. 🤗
### How to Help { #how-to-help }
Follow the [guide on how to help](https://tiangolo.com/open-source/help/#help-others-with-questions-in-github) here.
## Ask Questions { #ask-questions }
You can [create a new question](https://github.com/fastapi/fastapi/discussions/new?category=questions) in the GitHub repository, for example to:
@@ -68,7 +54,7 @@ You can [create a new question](https://github.com/fastapi/fastapi/discussions/n
## Join the Chat { #join-the-chat }
Join the 👥 [Discord chat server](https://discord.gg/VQjSZaeJmf) 👥 and hang out with others in the FastAPI community.
Join the 👥 [Discord chat server](https://discord.com/invite/VQjSZaeJmf) 👥 and hang out with others in the FastAPI community.
/// tip
@@ -85,3 +71,9 @@ Keep in mind that as chats allow more "free conversation", it's easy to ask ques
In GitHub, the template will guide you to write the right question so that you can more easily get a good answer, or even solve the problem yourself even before asking.
Conversations in the chat systems are also not as easily searchable as in GitHub, they get lost.
## Try FastAPI Cloud { #try-fastapi-cloud }
The main funding for FastAPI and friends comes from [**FastAPI Cloud**](https://fastapicloud.com), a platform to deploy FastAPI applications in a simple and fast way, with a single command, `fastapi deploy`.
FastAPI Cloud is built by the same team behind FastAPI. You can try it and consider it for your projects.
+2 -2
View File
@@ -54,11 +54,11 @@ All in a way that provided the best development experience for all the developer
## Requirements { #requirements }
After testing several alternatives, I decided that I was going to use [**Pydantic**](https://docs.pydantic.dev/) for its advantages.
After testing several alternatives, I decided that I was going to use [**Pydantic**](https://pydantic.dev/docs/) for its advantages.
Then I contributed to it, to make it fully compliant with JSON Schema, to support different ways to define constraint declarations, and to improve editor support (type checks, autocompletion) based on the tests in several editors.
During the development, I also contributed to [**Starlette**](https://www.starlette.dev/), the other key requirement.
During the development, I also contributed to [**Starlette**](https://starlette.dev/), the other key requirement.
## Development { #development }
@@ -66,7 +66,7 @@ The `scope` `dict` and `receive` function are both part of the ASGI specificatio
And those two things, `scope` and `receive`, are what is needed to create a new `Request` instance.
To learn more about the `Request` check [Starlette's docs about Requests](https://www.starlette.dev/requests/).
To learn more about the `Request` check [Starlette's docs about Requests](https://starlette.dev/requests/).
///
+1 -1
View File
@@ -45,7 +45,7 @@ The parameter `summary` is available in OpenAPI 3.1.0 and above, supported by Fa
Using the information above, you can use the same utility function to generate the OpenAPI schema and override each part that you need.
For example, let's add [ReDoc's OpenAPI extension to include a custom logo](https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md#x-logo).
For example, let's add [ReDoc's OpenAPI extension to include a custom logo](https://github.com/Redocly/redoc/blob/main/docs/redoc-vendor-extensions.md#x-logo).
### Normal **FastAPI** { #normal-fastapi }
+1 -1
View File
@@ -21,7 +21,7 @@ Here are some of the **GraphQL** libraries that have **ASGI** support. You could
* [Strawberry](https://strawberry.rocks/) 🍓
* With [docs for FastAPI](https://strawberry.rocks/docs/integrations/fastapi)
* [Ariadne](https://ariadnegraphql.org/)
* With [docs for FastAPI](https://ariadnegraphql.org/docs/fastapi-integration)
* With [docs for FastAPI](https://ariadnegraphql.org/server/Integrations/fastapi-integration)
* [Tartiflette](https://tartiflette.io/)
* With [Tartiflette ASGI](https://tartiflette.github.io/tartiflette-asgi/) to provide ASGI integration
* [Graphene](https://graphene-python.org/)
@@ -24,7 +24,7 @@ If you have an old FastAPI app with Pydantic v1, here I'll show you how to migra
## Official Guide { #official-guide }
Pydantic has an official [Migration Guide](https://docs.pydantic.dev/latest/migration/) from v1 to v2.
Pydantic has an official [Migration Guide](https://pydantic.dev/docs/validation/latest/get-started/migration/) from v1 to v2.
It also includes what has changed, how validations are now more correct and strict, possible caveats, etc.
+19 -17
View File
@@ -110,7 +110,7 @@ The key features are:
</div>
<div class="fastapi-opinions__panel" id="fo-panel-uber" role="tabpanel" aria-labelledby="fo-tab-uber" tabindex="0" hidden>
<blockquote class="fastapi-opinions__quote">"We adopted the <strong>FastAPI</strong> library to spawn a <strong>REST</strong> server that can be queried to obtain <strong>predictions</strong>." <em>[for Ludwig]</em></blockquote>
<div class="fastapi-opinions__attr">— Piero Molino, Yaroslav Dudin, Sai Sumanth Miryala, <strong>Uber</strong> <a href="https://eng.uber.com/ludwig-v0-2/">(ref)</a></div>
<div class="fastapi-opinions__attr">— Piero Molino, Yaroslav Dudin, Sai Sumanth Miryala, <strong>Uber</strong> <a href="https://www.uber.com/us/en/blog/ludwig-v0-2/">(ref)</a></div>
</div>
<div class="fastapi-opinions__panel" id="fo-panel-netflix" role="tabpanel" aria-labelledby="fo-tab-netflix" tabindex="0" hidden>
<blockquote class="fastapi-opinions__quote">"<strong>Netflix</strong> is pleased to announce the open-source release of our <strong>crisis management</strong> orchestration framework: <strong>Dispatch</strong>!" <em>[built with FastAPI]</em></blockquote>
@@ -133,7 +133,7 @@ The key features are:
"_We adopted the **FastAPI** library to spawn a **REST** server that can be queried to obtain **predictions**. [for Ludwig]_"
<div style="text-align: right; margin-right: 10%;">Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - <strong>Uber</strong> <a href="https://eng.uber.com/ludwig-v0-2/"><small>(ref)</small></a></div>
<div style="text-align: right; margin-right: 10%;">Piero Molino, Yaroslav Dudin, and Sai Sumanth Miryala - <strong>Uber</strong> <a href="https://www.uber.com/us/en/blog/ludwig-v0-2/"><small>(ref)</small></a></div>
---
@@ -175,17 +175,17 @@ If you are building a <abbr title="Command Line Interface">CLI</abbr> app to be
FastAPI stands on the shoulders of giants:
* [Starlette](https://www.starlette.dev/) for the web parts.
* [Pydantic](https://docs.pydantic.dev/) for the data parts.
* [Starlette](https://starlette.dev/) for the web parts.
* [Pydantic](https://pydantic.dev/docs/) for the data parts.
## Installation { #installation }
Create and activate a [virtual environment](https://fastapi.tiangolo.com/virtual-environments/) and then install FastAPI:
First, [install `uv`](https://docs.astral.sh/uv/getting-started/installation/), and then add FastAPI to your project:
<div class="termy">
```console
$ pip install "fastapi[standard]"
$ uv add "fastapi[standard]"
---> 100%
```
@@ -194,6 +194,8 @@ $ pip install "fastapi[standard]"
**Note**: Make sure you put `"fastapi[standard]"` in quotes to ensure it works in all terminals.
If you prefer to use `pip`, install `fastapi[standard]` inside a virtual environment. See the [installation guide](tutorial/#install-fastapi) for the alternative steps.
## Example { #example }
### Create it { #create-it }
@@ -250,7 +252,7 @@ Run the server with:
<div class="termy">
```console
$ fastapi dev
$ uv run fastapi dev
╭────────── FastAPI CLI - Development mode ───────────╮
│ │
@@ -277,7 +279,7 @@ INFO: Application startup complete.
<details markdown="1">
<summary>About the command <code>fastapi dev</code>...</summary>
The command `fastapi dev` reads your `main.py` file automatically, detects the **FastAPI** app in it, and starts a server using [Uvicorn](https://www.uvicorn.dev).
The command `fastapi dev` reads your `main.py` file automatically, detects the **FastAPI** app in it, and starts a server using [Uvicorn](https://uvicorn.dev).
By default, `fastapi dev` will start with auto-reload enabled for local development.
@@ -314,7 +316,7 @@ You will see the automatic interactive API documentation (provided by [Swagger U
And now, go to [http://127.0.0.1:8000/redoc](http://127.0.0.1:8000/redoc).
You will see the alternative automatic documentation (provided by [ReDoc](https://github.com/Rebilly/ReDoc)):
You will see the alternative automatic documentation (provided by [ReDoc](https://github.com/Redocly/redoc)):
![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png)
@@ -497,7 +499,7 @@ You can optionally deploy your FastAPI app to [FastAPI Cloud](https://fastapiclo
<div class="termy">
```console
$ fastapi deploy
$ uv run fastapi deploy
Deploying to FastAPI Cloud...
@@ -540,7 +542,7 @@ FastAPI depends on Pydantic and Starlette.
### `standard` Dependencies { #standard-dependencies }
When you install FastAPI with `pip install "fastapi[standard]"` it comes with the `standard` group of optional dependencies:
When you install FastAPI with `uv add "fastapi[standard]"` it comes with the `standard` group of optional dependencies:
Used by Pydantic:
@@ -554,17 +556,17 @@ Used by Starlette:
Used by FastAPI:
* [`uvicorn`](https://www.uvicorn.dev) - for the server that loads and serves your application. This includes `uvicorn[standard]`, which includes some dependencies (e.g. `uvloop`) needed for high performance serving.
* [`uvicorn`](https://uvicorn.dev) - for the server that loads and serves your application. This includes `uvicorn[standard]`, which includes some dependencies (e.g. `uvloop`) needed for high performance serving.
* `fastapi-cli[standard]` - to provide the `fastapi` command.
* This includes `fastapi-cloud-cli`, which allows you to deploy your FastAPI application to [FastAPI Cloud](https://fastapicloud.com).
### Without `standard` Dependencies { #without-standard-dependencies }
If you don't want to include the `standard` optional dependencies, you can install with `pip install fastapi` instead of `pip install "fastapi[standard]"`.
If you don't want to include the `standard` optional dependencies, you can install with `uv add fastapi` instead of `uv add "fastapi[standard]"`.
### Without `fastapi-cloud-cli` { #without-fastapi-cloud-cli }
If you want to install FastAPI with the standard dependencies but without the `fastapi-cloud-cli`, you can install with `pip install "fastapi[standard-no-fastapi-cloud-cli]"`.
If you want to install FastAPI with the standard dependencies but without the `fastapi-cloud-cli`, you can install with `uv add "fastapi[standard-no-fastapi-cloud-cli]"`.
### Additional Optional Dependencies { #additional-optional-dependencies }
@@ -572,13 +574,13 @@ There are some additional dependencies you might want to install.
Additional optional Pydantic dependencies:
* [`pydantic-settings`](https://docs.pydantic.dev/latest/usage/pydantic_settings/) - for settings management.
* [`pydantic-extra-types`](https://docs.pydantic.dev/latest/usage/types/extra_types/extra_types/) - for extra types to be used with Pydantic.
* [`pydantic-settings`](https://pydantic.dev/docs/validation/latest/concepts/pydantic_settings/) - for settings management.
* [`pydantic-extra-types`](https://github.com/pydantic/pydantic-extra-types) - for extra types to be used with Pydantic.
Additional optional FastAPI dependencies:
* [`orjson`](https://github.com/ijl/orjson) - Required if you want to use `ORJSONResponse`.
* [`ujson`](https://github.com/esnme/ultrajson) - Required if you want to use `UJSONResponse`.
* [`ujson`](https://github.com/ultrajson/ultrajson) - Required if you want to use `UJSONResponse`.
## License { #license }
-10
View File
@@ -13,13 +13,3 @@ I normally give the final review to each PR before merging them. I make the fina
There's a team of people that help manage and maintain the project. 😎
Learn more about it in [tiangolo.com - GitHub FastAPI](https://tiangolo.com/github-fastapi/).
## FastAPI Experts
The people that help others the most in GitHub Discussions can become [**FastAPI Experts**](./fastapi-people.md#fastapi-experts).
This is normally the best way to contribute to the project.
## External Help
External help is very much appreciated. There are many ways to [help](./help-fastapi.md). ☕️
+2 -2
View File
@@ -4,13 +4,13 @@ Templates, while they typically come with a specific setup, are designed to be f
You can use this template to get started, as it includes a lot of the initial setup, security, database and some API endpoints already done for you.
GitHub Repository: [Full Stack FastAPI Template](https://github.com/tiangolo/full-stack-fastapi-template)
GitHub Repository: [Full Stack FastAPI Template](https://github.com/fastapi/full-stack-fastapi-template)
## Full Stack FastAPI Template - Technology Stack and Features { #full-stack-fastapi-template-technology-stack-and-features }
- ⚡ [**FastAPI**](https://fastapi.tiangolo.com) for the Python backend API.
- 🧰 [SQLModel](https://sqlmodel.tiangolo.com) for the Python SQL database interactions (ORM).
- 🔍 [Pydantic](https://docs.pydantic.dev), used by FastAPI, for the data validation and settings management.
- 🔍 [Pydantic](https://pydantic.dev/docs/), used by FastAPI, for the data validation and settings management.
- 💾 [PostgreSQL](https://www.postgresql.org) as the SQL database.
- 🚀 [React](https://react.dev) for the frontend.
- 💃 Using TypeScript, hooks, Vite, and other parts of a modern frontend stack.
+2 -2
View File
@@ -269,7 +269,7 @@ It doesn't mean "`one_person` is the **class** called `Person`".
## Pydantic models { #pydantic-models }
[Pydantic](https://docs.pydantic.dev/) is a Python library to perform data validation.
[Pydantic](https://pydantic.dev/docs/) is a Python library to perform data validation.
You declare the "shape" of the data as classes with attributes.
@@ -285,7 +285,7 @@ An example from the official Pydantic docs:
/// note
To learn more about [Pydantic, check its docs](https://docs.pydantic.dev/).
To learn more about [Pydantic, check its docs](https://pydantic.dev/docs/).
///
+49 -2
View File
@@ -7,6 +7,53 @@ hide:
## Latest Changes
## 0.140.4 (2026-07-27)
### Refactors
* ⚡️ Skip unused dependency repeat bookkeeping. PR [#16069](https://github.com/fastapi/fastapi/pull/16069) by [@tiangolo](https://github.com/tiangolo).
## 0.140.3 (2026-07-27)
### Refactors
* ⚡️ Avoid repeated dependency flattening in OpenAPI. PR [#16067](https://github.com/fastapi/fastapi/pull/16067) by [@tiangolo](https://github.com/tiangolo).
## 0.140.2 (2026-07-27)
### Refactors
* ⚡️ Stop retaining flat dependency trees. PR [#16065](https://github.com/fastapi/fastapi/pull/16065) by [@tiangolo](https://github.com/tiangolo).
### Internal
* 👷 Add new memory benchmark. PR [#16064](https://github.com/fastapi/fastapi/pull/16064) by [@tiangolo](https://github.com/tiangolo).
## 0.140.1 (2026-07-27)
### Refactors
* ♻️ Update the lru_cache limit for dependencies to account for large apps. PR [#16062](https://github.com/fastapi/fastapi/pull/16062) by [@tiangolo](https://github.com/tiangolo).
## 0.140.0 (2026-07-24)
### Refactors
* ⚡️ Reduce memory usage in dependencies. PR [#16049](https://github.com/fastapi/fastapi/pull/16049) by [@tiangolo](https://github.com/tiangolo).
### Docs
* 📝 Fix links in docs. PR [#15967](https://github.com/fastapi/fastapi/pull/15967) by [@YuriiMotov](https://github.com/YuriiMotov).
* 📝 Add Library Skills documentation. PR [#16041](https://github.com/fastapi/fastapi/pull/16041) by [@tiangolo](https://github.com/tiangolo).
* 📝 Update docs to use uv projects by default. PR [#16032](https://github.com/fastapi/fastapi/pull/16032) by [@tiangolo](https://github.com/tiangolo).
* 📝 Restructure FastAPI People and related pages. PR [#16015](https://github.com/fastapi/fastapi/pull/16015) by [@tiangolo](https://github.com/tiangolo).
### Internal
* 👷 Add CI memory benchmark. PR [#16046](https://github.com/fastapi/fastapi/pull/16046) by [@tiangolo](https://github.com/tiangolo).
* 👥 Update FastAPI People - Sponsors. PR [#16027](https://github.com/fastapi/fastapi/pull/16027) by [@tiangolo](https://github.com/tiangolo).
* 🔥 Remove now-obsolete scripts to generate data for FastAPI People. PR [#16016](https://github.com/fastapi/fastapi/pull/16016) by [@tiangolo](https://github.com/tiangolo).
## 0.139.2 (2026-07-16)
### Fixes
@@ -4084,7 +4131,7 @@ There are **tests for both Pydantic v1 and v2**, and test **coverage** is kept a
* The attribute `schema_extra` for the internal class `Config` has been replaced by the key `json_schema_extra` in the new `model_config` dict.
* You can read more about it in the docs for [Declare Request Example Data](https://fastapi.tiangolo.com/tutorial/schema-extra-example/).
* When you install `"fastapi[all]"` it now also includes:
* [`pydantic-settings`](https://docs.pydantic.dev/latest/usage/pydantic_settings/) - for settings management.
* [`pydantic-settings`](https://pydantic.dev/docs/validation/latest/concepts/pydantic_settings/) - for settings management.
* [`pydantic-extra-types`](https://docs.pydantic.dev/latest/usage/types/extra_types/extra_types/) - for extra types to be used with Pydantic.
* Now Pydantic Settings is an additional optional package (included in `"fastapi[all]"`). To use settings you should now import `from pydantic_settings import BaseSettings` instead of importing from `pydantic` directly.
* You can read more about it in the docs for [Settings and Environment Variables](https://fastapi.tiangolo.com/advanced/settings/).
@@ -6892,7 +6939,7 @@ Note: all the previous parameters are still there, so it's still possible to dec
* Upgrade the compatible version of Starlette to `0.12.0`.
* This includes support for ASGI 3 (the latest version of the standard).
* It's now possible to use [Starlette's `StreamingResponse`](https://www.starlette.dev/responses/#streamingresponse) with iterators, like [file-like](https://docs.python.org/3/glossary.html#term-file-like-object) objects (as those returned by `open()`).
* It's now possible to use [Starlette's `StreamingResponse`](https://starlette.dev/responses/#streamingresponse) with iterators, like [file-like](https://docs.python.org/3/glossary.html#term-file-like-object) objects (as those returned by `open()`).
* It's now possible to use the low level utility `iterate_in_threadpool` from `starlette.concurrency` (for advanced scenarios).
* PR [#243](https://github.com/tiangolo/fastapi/pull/243).
+2 -2
View File
@@ -63,7 +63,7 @@ And then another background task generated at the *path operation function* will
## Technical Details { #technical-details }
The class `BackgroundTasks` comes directly from [`starlette.background`](https://www.starlette.dev/background/).
The class `BackgroundTasks` comes directly from [`starlette.background`](https://starlette.dev/background/).
It is imported/included directly into FastAPI so that you can import it from `fastapi` and avoid accidentally importing the alternative `BackgroundTask` (without the `s` at the end) from `starlette.background`.
@@ -71,7 +71,7 @@ By only using `BackgroundTasks` (and not `BackgroundTask`), it's then possible t
It's still possible to use `BackgroundTask` alone in FastAPI, but you have to create the object in your code and return a Starlette `Response` including it.
You can see more details in [Starlette's official docs for Background Tasks](https://www.starlette.dev/background/).
You can see more details in [Starlette's official docs for Background Tasks](https://starlette.dev/background/).
## Caveat { #caveat }
+2 -2
View File
@@ -487,7 +487,7 @@ That way the `fastapi` command will know where to find your app.
You could also pass the path to the command, like:
```console
$ fastapi dev app/main.py
$ uv run fastapi dev app/main.py
```
But you would have to remember to pass the correct path every time you call the `fastapi` command.
@@ -503,7 +503,7 @@ Now, run your app:
<div class="termy">
```console
$ fastapi dev
$ uv run fastapi dev
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
+1 -1
View File
@@ -96,7 +96,7 @@ Again, doing just that declaration, with **FastAPI** you get:
Apart from normal singular types like `str`, `int`, `float`, etc. you can use more complex singular types that inherit from `str`.
To see all the options you have, check out [Pydantic's Type Overview](https://docs.pydantic.dev/latest/concepts/types/). You will see some examples in the next chapter.
To see all the options you have, check out [Pydantic's Type Overview](https://pydantic.dev/docs/validation/latest/concepts/types/). You will see some examples in the next chapter.
For example, as in the `Image` model we have a `url` field, we can declare it to be an instance of Pydantic's `HttpUrl` instead of a `str`:
+1 -1
View File
@@ -6,7 +6,7 @@ A **request** body is data sent by the client to your API. A **response** body i
Your API almost always has to send a **response** body. But clients don't necessarily need to send **request bodies** all the time, sometimes they only request a path, maybe with some query parameters, but don't send a body.
To declare a **request** body, you use [Pydantic](https://docs.pydantic.dev/) models with all their power and benefits.
To declare a **request** body, you use [Pydantic](https://pydantic.dev/docs/) models with all their power and benefits.
/// note
+2 -2
View File
@@ -15,7 +15,7 @@ The main purpose of the `__name__ == "__main__"` is to have some code that is ex
<div class="termy">
```console
$ python myapp.py
$ uv run python myapp.py
```
</div>
@@ -35,7 +35,7 @@ If you run it with:
<div class="termy">
```console
$ python myapp.py
$ uv run python myapp.py
```
</div>
+2 -2
View File
@@ -36,7 +36,7 @@ Here are some of the additional data types you can use:
* `datetime.timedelta`:
* A Python `datetime.timedelta`.
* In requests and responses will be represented as a `float` of total seconds.
* Pydantic also allows representing it as an "ISO 8601 time diff encoding", [see the docs for more info](https://docs.pydantic.dev/latest/concepts/serialization/#custom-serializers).
* Pydantic also allows representing it as an "ISO 8601 time diff encoding", [see the docs for more info](https://pydantic.dev/docs/validation/latest/concepts/serialization/#custom-serializers).
* `frozenset`:
* In requests and responses, treated the same as a `set`:
* In requests, a list will be read, eliminating duplicates and converting it to a `set`.
@@ -49,7 +49,7 @@ Here are some of the additional data types you can use:
* `Decimal`:
* Standard Python `Decimal`.
* In requests and responses, handled the same as a `float`.
* You can check all the valid Pydantic data types here: [Pydantic data types](https://docs.pydantic.dev/latest/usage/types/types/).
* You can check all the valid Pydantic data types here: [Pydantic data types](https://pydantic.dev/docs/validation/latest/concepts/types/).
## Example { #example }
+1 -1
View File
@@ -166,7 +166,7 @@ To do that, use the standard Python type hint [`typing.Union`](https://docs.pyth
/// note
When defining a [`Union`](https://docs.pydantic.dev/latest/concepts/types/#unions), include the most specific type first, followed by the less specific type. In the example below, the more specific `PlaneItem` comes before `CarItem` in `Union[PlaneItem, CarItem]`.
When defining a [`Union`](https://pydantic.dev/docs/validation/latest/concepts/unions/), include the most specific type first, followed by the less specific type. In the example below, the more specific `PlaneItem` comes before `CarItem` in `Union[PlaneItem, CarItem]`.
///
+12 -6
View File
@@ -6,12 +6,18 @@ The simplest FastAPI file could look like this:
Copy that to a file `main.py`.
/// tip
FastAPI has an [official extension for VS Code](https://marketplace.visualstudio.com/items?itemName=FastAPILabs.fastapi-vscode) (and Cursor), which provides a lot of features, including a path operation explorer, path operation search, CodeLens navigation in tests (jump to definition from tests), and FastAPI Cloud deployment and logs, all from your editor.
///
Run the live server:
<div class="termy">
```console
$ <font color="#4E9A06">fastapi</font> dev
$ <font color="#4E9A06">uv run fastapi</font> dev
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
@@ -78,7 +84,7 @@ You will see the automatic interactive API documentation (provided by [Swagger U
And now, go to [http://127.0.0.1:8000/redoc](http://127.0.0.1:8000/redoc).
You will see the alternative automatic documentation (provided by [ReDoc](https://github.com/Rebilly/ReDoc)):
You will see the alternative automatic documentation (provided by [ReDoc](https://github.com/Redocly/redoc)):
![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png)
@@ -185,13 +191,13 @@ from backend.main import app
You can also pass the file path to the `fastapi dev` command, and it will guess the FastAPI app object to use:
```console
$ fastapi dev main.py
$ uv run fastapi dev main.py
```
Or, you can also pass the `--entrypoint` option to the `fastapi dev` command:
```console
$ fastapi dev --entrypoint main:app
$ uv run fastapi dev --entrypoint main:app
```
But you would have to remember to pass the correct path\entrypoint every time you call the `fastapi` command.
@@ -205,7 +211,7 @@ You can optionally deploy your FastAPI app to [FastAPI Cloud](https://fastapiclo
<div class="termy">
```console
$ fastapi deploy
$ uv run fastapi deploy
Deploying to FastAPI Cloud...
@@ -232,7 +238,7 @@ That's it! Now you can access your app at that URL. ✨
`FastAPI` is a class that inherits directly from `Starlette`.
You can use all the [Starlette](https://www.starlette.dev/) functionality with `FastAPI` too.
You can use all the [Starlette](https://starlette.dev/) functionality with `FastAPI` too.
///
+1 -1
View File
@@ -81,7 +81,7 @@ But in case you needed it for an advanced scenario, you can add custom headers:
## Install custom exception handlers { #install-custom-exception-handlers }
You can add custom exception handlers with [the same exception utilities from Starlette](https://www.starlette.dev/exceptions/).
You can add custom exception handlers with [the same exception utilities from Starlette](https://starlette.dev/exceptions/).
Let's say you have a custom exception `UnicornException` that you (or a library you use) might `raise`.
+55 -15
View File
@@ -10,12 +10,12 @@ It is also built to work as a future reference so you can come back and see exac
All the code blocks can be copied and used directly (they are actually tested Python files).
To run any of the examples, copy the code to a file `main.py`, and start `fastapi dev`:
To run any of the examples, copy the code to a file `main.py`, and start `fastapi dev` with `uv run`:
<div class="termy">
```console
$ <font color="#4E9A06">fastapi</font> dev
$ <font color="#4E9A06">uv run fastapi</font> dev
<span style="background-color:#009485"><font color="#D3D7CF"> FastAPI </font></span> Starting development server 🚀
@@ -60,35 +60,75 @@ Using it in your editor is what really shows you the benefits of FastAPI, seeing
## Install FastAPI { #install-fastapi }
The first step is to install FastAPI.
The first step is to set up your project and add FastAPI.
Make sure you create a [virtual environment](../virtual-environments.md), activate it, and then **install FastAPI**:
Install [`uv`](https://docs.astral.sh/uv/getting-started/installation/), then create a project and add FastAPI:
<div class="termy">
```console
$ pip install "fastapi[standard]"
$ uv init awesome-project --bare
$ cd awesome-project
$ uv add "fastapi[standard]"
---> 100%
```
</div>
`uv add` creates the project's virtual environment in `.venv`, adds FastAPI to `pyproject.toml`, and creates `uv.lock` so the same package versions can be installed later.
/// details | What these commands do
* `uv init`: create a new Python project.
* `awesome-project`: create the project in a new directory with this name.
* `--bare`: create only the minimal `pyproject.toml` file, without generating a sample `main.py`, `README.md`, or other files. You will create the application files yourself in the next steps of this tutorial.
Then `cd awesome-project` enters the new project directory before adding FastAPI.
`uv` will use a compatible Python version already installed on your system, or download one if needed.
When you run `uv add`, it selects compatible versions of FastAPI and all the packages FastAPI depends on. It records the exact versions in `uv.lock`, making it possible to install the same package versions later on another computer or when deploying the application.
Creating or updating this file is called [**locking** the project dependencies](https://docs.astral.sh/uv/concepts/projects/sync/). `uv` does this automatically when you add a package.
///
/// details | FastAPI installation options
When you install with `uv add "fastapi[standard]"` it comes with some default optional standard dependencies, including `fastapi-cloud-cli`, which allows you to deploy to [FastAPI Cloud](https://fastapicloud.com).
If you don't want to have those optional dependencies, you can instead install `uv add fastapi`.
If you want to install the standard dependencies but without the `fastapi-cloud-cli`, you can install with `uv add "fastapi[standard-no-fastapi-cloud-cli]"`.
///
/// details | Using `pip` instead
If you prefer to manage a virtual environment and packages manually, create and activate a virtual environment and then install FastAPI with `pip install "fastapi[standard]"`.
Read the [Virtual Environments guide](https://tiangolo.com/guides/virtual-environments/) for the detailed steps.
///
## AI Agent Skills { #ai-agent-skills }
FastAPI includes an official skill for AI coding agents. It is bundled with the package, so its guidance stays aligned with the version of FastAPI installed in your project and updates when you update FastAPI.
After installing FastAPI in your project, you can install the skill with <a href="https://library-skills.io">Library Skills</a>:
```bash
uvx library-skills
```
/// note
When you install with `pip install "fastapi[standard]"` it comes with some default optional standard dependencies, including `fastapi-cloud-cli`, which allows you to deploy to [FastAPI Cloud](https://fastapicloud.com).
If you don't want to have those optional dependencies, you can instead install `pip install fastapi`.
If you want to install the standard dependencies but without the `fastapi-cloud-cli`, you can install with `pip install "fastapi[standard-no-fastapi-cloud-cli]"`.
`uvx` is an alias for `uv tool run`. It runs Library Skills in a temporary, isolated environment while Library Skills scans the packages installed in your project.
///
/// tip
FastAPI has an [official extension for VS Code](https://marketplace.visualstudio.com/items?itemName=FastAPILabs.fastapi-vscode) (and Cursor), which provides a lot of features, including a path operation explorer, path operation search, CodeLens navigation in tests (jump to definition from tests), and FastAPI Cloud deployment and logs, all from your editor.
///
The skill is compatible with Codex, Claude Code, Cursor, GitHub Copilot, Gemini CLI, Pi, OpenCode, and most other coding agents. For Claude Code, select `.claude/skills` when asked where to install the skill.
## Advanced User Guide { #advanced-user-guide }
+1 -1
View File
@@ -37,7 +37,7 @@ The middleware function receives:
Keep in mind that custom proprietary headers can be added [using the `X-` prefix](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers).
But if you have custom headers that you want a client in a browser to be able to see, you need to add them to your CORS configurations ([CORS (Cross-Origin Resource Sharing)](cors.md)) using the parameter `expose_headers` documented in [Starlette's CORS docs](https://www.starlette.dev/middleware/#corsmiddleware).
But if you have custom headers that you want a client in a browser to be able to see, you need to add them to your CORS configurations ([CORS (Cross-Origin Resource Sharing)](cors.md)) using the parameter `expose_headers` documented in [Starlette's CORS docs](https://starlette.dev/middleware/#corsmiddleware).
///
+2 -2
View File
@@ -92,7 +92,7 @@ Notice that the path parameter is declared to be an integer.
## Standards-based benefits, alternative documentation { #standards-based-benefits-alternative-documentation }
And because the generated schema is from the [OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md) standard, there are many compatible tools.
And because the generated schema is from the [OpenAPI](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md) standard, there are many compatible tools.
Because of this, **FastAPI** itself provides an alternative API documentation (using ReDoc), which you can access at [http://127.0.0.1:8000/redoc](http://127.0.0.1:8000/redoc):
@@ -102,7 +102,7 @@ The same way, there are many compatible tools. Including code generation tools f
## Pydantic { #pydantic }
All the data validation is performed under the hood by [Pydantic](https://docs.pydantic.dev/), so you get all the benefits from it. And you know you are in good hands.
All the data validation is performed under the hood by [Pydantic](https://pydantic.dev/docs/), so you get all the benefits from it. And you know you are in good hands.
You can use the same type declarations with `str`, `float`, `bool` and many other complex data types.
@@ -370,11 +370,11 @@ There could be cases where you need to do some **custom validation** that can't
In those cases, you can use a **custom validator function** that is applied after the normal validation (e.g. after validating that the value is a `str`).
You can achieve that using [Pydantic's `AfterValidator`](https://docs.pydantic.dev/latest/concepts/validators/#field-after-validator) inside of `Annotated`.
You can achieve that using [Pydantic's `AfterValidator`](https://pydantic.dev/docs/validation/latest/concepts/validators/#field-after-validator) inside of `Annotated`.
/// tip
Pydantic also has [`BeforeValidator`](https://docs.pydantic.dev/latest/concepts/validators/#field-before-validator) and others. 🤓
Pydantic also has [`BeforeValidator`](https://pydantic.dev/docs/validation/latest/concepts/validators/#field-before-validator) and others. 🤓
///
+2 -2
View File
@@ -6,10 +6,10 @@ You can define files to be uploaded by the client using `File`.
To receive uploaded files, first install [`python-multipart`](https://github.com/Kludex/python-multipart).
Make sure you create a [virtual environment](../virtual-environments.md), activate it, and then install it, for example:
Add it to your project:
```console
$ pip install python-multipart
$ uv add python-multipart
```
This is because uploaded files are sent as "form data".
+2 -2
View File
@@ -6,10 +6,10 @@ You can use **Pydantic models** to declare **form fields** in FastAPI.
To use forms, first install [`python-multipart`](https://github.com/Kludex/python-multipart).
Make sure you create a [virtual environment](../virtual-environments.md), activate it, and then install it, for example:
Add it to your project:
```console
$ pip install python-multipart
$ uv add python-multipart
```
///
@@ -6,10 +6,10 @@ You can define files and form fields at the same time using `File` and `Form`.
To receive uploaded files and/or form data, first install [`python-multipart`](https://github.com/Kludex/python-multipart).
Make sure you create a [virtual environment](../virtual-environments.md), activate it, and then install it, for example:
Add it to your project:
```console
$ pip install python-multipart
$ uv add python-multipart
```
///
+2 -2
View File
@@ -6,10 +6,10 @@ When you need to receive form fields instead of JSON, you can use `Form`.
To use forms, first install [`python-multipart`](https://github.com/Kludex/python-multipart).
Make sure you create a [virtual environment](../virtual-environments.md), activate it, and then install it, for example:
Add it to your project:
```console
$ pip install python-multipart
$ uv add python-multipart
```
///
+4 -4
View File
@@ -76,16 +76,16 @@ Here we are declaring a `UserIn` model, it will contain a plaintext password:
To use `EmailStr`, first install [`email-validator`](https://github.com/JoshData/python-email-validator).
Make sure you create a [virtual environment](../virtual-environments.md), activate it, and then install it, for example:
Add it to your project:
```console
$ pip install email-validator
$ uv add email-validator
```
or with:
```console
$ pip install "pydantic[email]"
$ uv add "pydantic[email]"
```
///
@@ -258,7 +258,7 @@ You can also use:
* `response_model_exclude_defaults=True`
* `response_model_exclude_none=True`
as described in [the Pydantic docs](https://docs.pydantic.dev/1.10/usage/exporting_models/#modeldict) for `exclude_defaults` and `exclude_none`.
as described in [the Pydantic docs](https://pydantic.dev/docs/validation/latest/concepts/serialization/#excluding-and-including-fields-based-on-their-value) for `exclude_defaults` and `exclude_none`.
///
@@ -12,7 +12,7 @@ You can declare `examples` for a Pydantic model that will be added to the genera
That extra info will be added as-is to the output **JSON Schema** for that model, and it will be used in the API docs.
You can use the attribute `model_config` that takes a `dict` as described in [Pydantic's docs: Configuration](https://docs.pydantic.dev/latest/api/config/).
You can use the attribute `model_config` that takes a `dict` as described in [Pydantic's docs: Configuration](https://pydantic.dev/docs/validation/latest/api/pydantic/config/).
You can set `"json_schema_extra"` with a `dict` containing any additional data you would like to show up in the generated JSON Schema, including `examples`.
@@ -26,14 +26,14 @@ Copy the example in a file `main.py`:
/// note
The [`python-multipart`](https://github.com/Kludex/python-multipart) package is automatically installed with **FastAPI** when you run the `pip install "fastapi[standard]"` command.
The [`python-multipart`](https://github.com/Kludex/python-multipart) package is automatically installed with **FastAPI** when you run the `uv add "fastapi[standard]"` command.
However, if you use the `pip install fastapi` command, the `python-multipart` package is not included by default.
However, if you use the `uv add fastapi` command, the `python-multipart` package is not included by default.
To install it manually, make sure you create a [virtual environment](../../virtual-environments.md), activate it, and then install it with:
To install it manually, add it to your project with:
```console
$ pip install python-multipart
$ uv add python-multipart
```
This is because **OAuth2** uses "form data" for sending the `username` and `password`.
@@ -45,7 +45,7 @@ Run the example with:
<div class="termy">
```console
$ fastapi dev
$ uv run fastapi dev
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
+4 -4
View File
@@ -30,12 +30,12 @@ If you want to play with JWT tokens and see how they work, check [https://jwt.io
We need to install `PyJWT` to generate and verify the JWT tokens in Python.
Make sure you create a [virtual environment](../../virtual-environments.md), activate it, and then install `pyjwt`:
Add `pyjwt` to your project:
<div class="termy">
```console
$ pip install pyjwt
$ uv add pyjwt
---> 100%
```
@@ -72,12 +72,12 @@ It supports many secure hashing algorithms and utilities to work with them.
The recommended algorithm is "Argon2".
Make sure you create a [virtual environment](../../virtual-environments.md), activate it, and then install pwdlib with Argon2:
Add `pwdlib` with Argon2 to your project:
<div class="termy">
```console
$ pip install "pwdlib[argon2]"
$ uv add "pwdlib[argon2]"
---> 100%
```
+4 -4
View File
@@ -34,12 +34,12 @@ This is a very simple and short tutorial, if you want to learn about databases i
## Install `SQLModel` { #install-sqlmodel }
First, make sure you create your [virtual environment](../virtual-environments.md), activate it, and then install `sqlmodel`:
Add `sqlmodel` to your project:
<div class="termy">
```console
$ pip install sqlmodel
$ uv add sqlmodel
---> 100%
```
@@ -152,7 +152,7 @@ You can run the app:
<div class="termy">
```console
$ fastapi dev
$ uv run fastapi dev
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
@@ -337,7 +337,7 @@ You can run the app again:
<div class="termy">
```console
$ fastapi dev
$ uv run fastapi dev
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```
+1 -1
View File
@@ -45,4 +45,4 @@ All these parameters can be different than "`static`", adjust them to the needs
## More info { #more-info }
For more details and options check [Starlette's docs about Static Files](https://www.starlette.dev/staticfiles/).
For more details and options check [Starlette's docs about Static Files](https://starlette.dev/staticfiles/).
+6 -6
View File
@@ -1,6 +1,6 @@
# Testing { #testing }
Thanks to [Starlette](https://www.starlette.dev/testclient/), testing **FastAPI** applications is easy and enjoyable.
Thanks to [Starlette](https://starlette.dev/testclient/), testing **FastAPI** applications is easy and enjoyable.
It is based on [HTTPX](https://www.python-httpx.org), which in turn is designed based on Requests, so it's very familiar and intuitive.
@@ -12,10 +12,10 @@ With it, you can use [pytest](https://docs.pytest.org/) directly with **FastAPI*
To use `TestClient`, first install [`httpx`](https://www.python-httpx.org).
Make sure you create a [virtual environment](../virtual-environments.md), activate it, and then install it, for example:
Add it to your project:
```console
$ pip install httpx
$ uv add httpx
```
///
@@ -156,12 +156,12 @@ If you have a Pydantic model in your test and you want to send its data to the a
After that, you just need to install `pytest`.
Make sure you create a [virtual environment](../virtual-environments.md), activate it, and then install it, for example:
Add it to your project:
<div class="termy">
```console
$ pip install pytest
$ uv add pytest
---> 100%
```
@@ -175,7 +175,7 @@ Run the tests with:
<div class="termy">
```console
$ pytest
$ uv run pytest
================ test session starts ================
platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1
+10 -839
View File
@@ -1,864 +1,35 @@
# Virtual Environments { #virtual-environments }
When you work in Python projects you probably should use a **virtual environment** (or a similar mechanism) to isolate the packages you install for each project.
When you work with Python projects, you should use a **virtual environment** to isolate the packages installed for each project.
/// note
If you already know about virtual environments, how to create them and use them, you might want to skip this section. 🤓
///
/// tip
A **virtual environment** is different than an **environment variable**.
An **environment variable** is a variable in the system that can be used by programs.
A **virtual environment** is a directory with some files in it.
///
/// note
This page will teach you how to use **virtual environments** and how they work.
If you are ready to adopt a **tool that manages everything** for you (including installing Python), try [uv](https://github.com/astral-sh/uv).
///
For FastAPI projects, I recommend using [uv](https://docs.astral.sh/uv/) to manage the project, its dependencies, and its virtual environment.
## Create a Project { #create-a-project }
First, create a directory for your project.
What I normally do is that I create a directory named `code` inside my home/user directory.
And inside of that I create one directory per project.
Install `uv` using the [official installation guide](https://docs.astral.sh/uv/getting-started/installation/), and then create a project:
<div class="termy">
```console
// Go to the home directory
$ cd
// Create a directory for all your code projects
$ mkdir code
// Enter into that code directory
$ cd code
// Create a directory for this project
$ mkdir awesome-project
// Enter into that project directory
$ uv init awesome-project --bare
$ cd awesome-project
$ uv add "fastapi[standard]"
```
</div>
## Create a Virtual Environment { #create-a-virtual-environment }
`uv` creates a virtual environment for the project automatically. You don't need to create or activate one yourself.
When you start working on a Python project **for the first time**, create a virtual environment **<dfn title="there are other options, this is a simple guideline">inside your project</dfn>**.
/// tip
You only need to do this **once per project**, not every time you work.
///
//// tab | `venv`
To create a virtual environment, you can use the `venv` module that comes with Python.
Run commands inside the project environment with `uv run`, for example:
<div class="termy">
```console
$ python -m venv .venv
$ uv run fastapi dev
```
</div>
/// details | What that command means
## Learn More { #learn-more }
* `python`: use the program called `python`
* `-m`: call a module as a script, we'll tell it which module next
* `venv`: use the module called `venv` that normally comes installed with Python
* `.venv`: create the virtual environment in the new directory `.venv`
///
////
//// tab | `uv`
If you have [`uv`](https://github.com/astral-sh/uv) installed, you can use it to create a virtual environment.
<div class="termy">
```console
$ uv venv
```
</div>
/// tip
By default, `uv` will create a virtual environment in a directory called `.venv`.
But you could customize it by passing an additional argument with the directory name.
///
////
That command creates a new virtual environment in a directory called `.venv`.
/// details | `.venv` or other name
You could create the virtual environment in a different directory, but there's a convention of calling it `.venv`.
///
## Activate the Virtual Environment { #activate-the-virtual-environment }
Activate the new virtual environment so that any Python command you run or package you install uses it.
/// tip
Do this **every time** you start a **new terminal session** to work on the project.
///
//// tab | Linux, macOS
<div class="termy">
```console
$ source .venv/bin/activate
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```console
$ .venv\Scripts\Activate.ps1
```
</div>
////
//// tab | Windows Bash
Or if you use Bash for Windows (e.g. [Git Bash](https://gitforwindows.org/)):
<div class="termy">
```console
$ source .venv/Scripts/activate
```
</div>
////
/// tip
Every time you install a **new package** in that environment, **activate** the environment again.
This makes sure that if you use a **terminal (<abbr title="command line interface">CLI</abbr>) program** installed by that package, you use the one from your virtual environment and not any other that could be installed globally, probably with a different version than what you need.
///
## Check the Virtual Environment is Active { #check-the-virtual-environment-is-active }
Check that the virtual environment is active (the previous command worked).
/// tip
This is **optional**, but it's a good way to **check** that everything is working as expected and you are using the virtual environment you intended.
///
//// tab | Linux, macOS, Windows Bash
<div class="termy">
```console
$ which python
/home/user/code/awesome-project/.venv/bin/python
```
</div>
If it shows the `python` binary at `.venv/bin/python`, inside of your project (in this case `awesome-project`), then it worked. 🎉
////
//// tab | Windows PowerShell
<div class="termy">
```console
$ Get-Command python
C:\Users\user\code\awesome-project\.venv\Scripts\python
```
</div>
If it shows the `python` binary at `.venv\Scripts\python`, inside of your project (in this case `awesome-project`), then it worked. 🎉
////
## Upgrade `pip` { #upgrade-pip }
/// tip
If you use [`uv`](https://github.com/astral-sh/uv) you would use it to install things instead of `pip`, so you don't need to upgrade `pip`. 😎
///
If you are using `pip` to install packages (it comes by default with Python), you should **upgrade** it to the latest version.
Many exotic errors while installing a package are solved by just upgrading `pip` first.
/// tip
You would normally do this **once**, right after you create the virtual environment.
///
Make sure the virtual environment is active (with the command above) and then run:
<div class="termy">
```console
$ python -m pip install --upgrade pip
---> 100%
```
</div>
/// tip
Sometimes, you might get a **`No module named pip`** error when trying to upgrade pip.
If this happens, install and upgrade pip using the command below:
<div class="termy">
```console
$ python -m ensurepip --upgrade
---> 100%
```
</div>
This command will install pip if it is not already installed and also ensure that the installed version of pip is at least as recent as the one available in `ensurepip`.
///
## Add `.gitignore` { #add-gitignore }
If you are using **Git** (you should), add a `.gitignore` file to exclude everything in your `.venv` from Git.
/// tip
If you used [`uv`](https://github.com/astral-sh/uv) to create the virtual environment, it already did this for you, you can skip this step. 😎
///
/// tip
Do this **once**, right after you create the virtual environment.
///
<div class="termy">
```console
$ echo "*" > .venv/.gitignore
```
</div>
/// details | What that command means
* `echo "*"`: will "print" the text `*` in the terminal (the next part changes that a bit)
* `>`: anything printed to the terminal by the command to the left of `>` should not be printed but instead written to the file that goes to the right of `>`
* `.gitignore`: the name of the file where the text should be written
And `*` for Git means "everything". So, it will ignore everything in the `.venv` directory.
That command will create a file `.gitignore` with the content:
```gitignore
*
```
///
## Install Packages { #install-packages }
After activating the environment, you can install packages in it.
/// tip
Do this **once** when installing or upgrading the packages your project needs.
If you need to upgrade a version or add a new package you would **do this again**.
///
### Install Packages Directly { #install-packages-directly }
If you're in a hurry and don't want to use a file to declare your project's package requirements, you can install them directly.
/// tip
It's a (very) good idea to put the packages and versions your program needs in a file (for example `requirements.txt` or `pyproject.toml`).
///
//// tab | `pip`
<div class="termy">
```console
$ pip install "fastapi[standard]"
---> 100%
```
</div>
////
//// tab | `uv`
If you have [`uv`](https://github.com/astral-sh/uv):
<div class="termy">
```console
$ uv pip install "fastapi[standard]"
---> 100%
```
</div>
////
### Install from `requirements.txt` { #install-from-requirements-txt }
If you have a `requirements.txt`, you can now use it to install its packages.
//// tab | `pip`
<div class="termy">
```console
$ pip install -r requirements.txt
---> 100%
```
</div>
////
//// tab | `uv`
If you have [`uv`](https://github.com/astral-sh/uv):
<div class="termy">
```console
$ uv pip install -r requirements.txt
---> 100%
```
</div>
////
/// details | `requirements.txt`
A `requirements.txt` with some packages could look like:
```requirements.txt
fastapi[standard]==0.113.0
pydantic==2.8.0
```
///
## Run Your Program { #run-your-program }
After you activated the virtual environment, you can run your program, and it will use the Python inside of your virtual environment with the packages you installed there.
<div class="termy">
```console
$ python main.py
Hello World
```
</div>
## Configure Your Editor { #configure-your-editor }
You would probably use an editor, make sure you configure it to use the same virtual environment you created (it will probably autodetect it) so that you can get autocompletion and inline errors.
For example:
* [VS Code](https://code.visualstudio.com/docs/python/environments#_select-and-activate-an-environment)
* [PyCharm](https://www.jetbrains.com/help/pycharm/creating-virtual-environment.html)
/// tip
You normally have to do this only **once**, when you create the virtual environment.
///
## Deactivate the Virtual Environment { #deactivate-the-virtual-environment }
Once you are done working on your project you can **deactivate** the virtual environment.
<div class="termy">
```console
$ deactivate
```
</div>
This way, when you run `python` it won't try to run it from that virtual environment with the packages installed there.
## Ready to Work { #ready-to-work }
Now you're ready to start working on your project.
/// tip
Do you want to understand what all that above is?
Continue reading. 👇🤓
///
## Why Virtual Environments { #why-virtual-environments }
To work with FastAPI you need to install [Python](https://www.python.org/).
After that, you would need to **install** FastAPI and any other **packages** you want to use.
To install packages you would normally use the `pip` command that comes with Python (or similar alternatives).
Nevertheless, if you just use `pip` directly, the packages would be installed in your **global Python environment** (the global installation of Python).
### The Problem { #the-problem }
So, what's the problem with installing packages in the global Python environment?
At some point, you will probably end up writing many different programs that depend on **different packages**. And some of these projects you work on will depend on **different versions** of the same package. 😱
For example, you could create a project called `philosophers-stone`, this program depends on another package called **`harry`, using the version `1`**. So, you need to install `harry`.
```mermaid
flowchart LR
stone(philosophers-stone) -->|requires| harry-1[harry v1]
```
Then, at some point later, you create another project called `prisoner-of-azkaban`, and this project also depends on `harry`, but this project needs **`harry` version `3`**.
```mermaid
flowchart LR
azkaban(prisoner-of-azkaban) --> |requires| harry-3[harry v3]
```
But now the problem is, if you install the packages globally (in the global environment) instead of in a local **virtual environment**, you will have to choose which version of `harry` to install.
If you want to run `philosophers-stone` you will need to first install `harry` version `1`, for example with:
<div class="termy">
```console
$ pip install "harry==1"
```
</div>
And then you would end up with `harry` version `1` installed in your global Python environment.
```mermaid
flowchart LR
subgraph global[global env]
harry-1[harry v1]
end
subgraph stone-project[philosophers-stone project]
stone(philosophers-stone) -->|requires| harry-1
end
```
But then if you want to run `prisoner-of-azkaban`, you will need to uninstall `harry` version `1` and install `harry` version `3` (or just installing version `3` would automatically uninstall version `1`).
<div class="termy">
```console
$ pip install "harry==3"
```
</div>
And then you would end up with `harry` version `3` installed in your global Python environment.
And if you try to run `philosophers-stone` again, there's a chance it would **not work** because it needs `harry` version `1`.
```mermaid
flowchart LR
subgraph global[global env]
harry-1[<strike>harry v1</strike>]
style harry-1 fill:#ccc,stroke-dasharray: 5 5
harry-3[harry v3]
end
subgraph stone-project[philosophers-stone project]
stone(philosophers-stone) -.-x|⛔️| harry-1
end
subgraph azkaban-project[prisoner-of-azkaban project]
azkaban(prisoner-of-azkaban) --> |requires| harry-3
end
```
/// tip
It's very common in Python packages to try the best to **avoid breaking changes** in **new versions**, but it's better to be safe, and install newer versions intentionally and when you can run the tests to check everything is working correctly.
///
Now, imagine that with **many** other **packages** that all your **projects depend on**. That's very difficult to manage. And you would probably end up running some projects with some **incompatible versions** of the packages, and not knowing why something isn't working.
Also, depending on your operating system (e.g. Linux, Windows, macOS), it could have come with Python already installed. And in that case it probably had some packages pre-installed with some specific versions **needed by your system**. If you install packages in the global Python environment, you could end up **breaking** some of the programs that came with your operating system.
## Where are Packages Installed { #where-are-packages-installed }
When you install Python, it creates some directories with some files on your computer.
Some of these directories are the ones in charge of having all the packages you install.
When you run:
<div class="termy">
```console
// Don't run this now, it's just an example 🤓
$ pip install "fastapi[standard]"
---> 100%
```
</div>
That will download a compressed file with the FastAPI code, normally from [PyPI](https://pypi.org/project/fastapi/).
It will also **download** files for other packages that FastAPI depends on.
Then it will **extract** all those files and put them in a directory on your computer.
By default, it will put those files downloaded and extracted in the directory that comes with your Python installation, that's the **global environment**.
## What are Virtual Environments { #what-are-virtual-environments }
The solution to the problems of having all the packages in the global environment is to use a **virtual environment for each project** you work on.
A virtual environment is a **directory**, very similar to the global one, where you can install the packages for a project.
This way, each project will have its own virtual environment (`.venv` directory) with its own packages.
```mermaid
flowchart TB
subgraph stone-project[philosophers-stone project]
stone(philosophers-stone) --->|requires| harry-1
subgraph venv1[.venv]
harry-1[harry v1]
end
end
subgraph azkaban-project[prisoner-of-azkaban project]
azkaban(prisoner-of-azkaban) --->|requires| harry-3
subgraph venv2[.venv]
harry-3[harry v3]
end
end
stone-project ~~~ azkaban-project
```
## What Does Activating a Virtual Environment Mean { #what-does-activating-a-virtual-environment-mean }
When you activate a virtual environment, for example with:
//// tab | Linux, macOS
<div class="termy">
```console
$ source .venv/bin/activate
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```console
$ .venv\Scripts\Activate.ps1
```
</div>
////
//// tab | Windows Bash
Or if you use Bash for Windows (e.g. [Git Bash](https://gitforwindows.org/)):
<div class="termy">
```console
$ source .venv/Scripts/activate
```
</div>
////
That command will create or modify some [environment variables](environment-variables.md) that will be available for the next commands.
One of those variables is the `PATH` variable.
/// tip
You can learn more about the `PATH` environment variable in the [Environment Variables](environment-variables.md#path-environment-variable) section.
///
Activating a virtual environment adds its path `.venv/bin` (on Linux and macOS) or `.venv\Scripts` (on Windows) to the `PATH` environment variable.
Let's say that before activating the environment, the `PATH` variable looked like this:
//// tab | Linux, macOS
```plaintext
/usr/bin:/bin:/usr/sbin:/sbin
```
That means that the system would look for programs in:
* `/usr/bin`
* `/bin`
* `/usr/sbin`
* `/sbin`
////
//// tab | Windows
```plaintext
C:\Windows\System32
```
That means that the system would look for programs in:
* `C:\Windows\System32`
////
After activating the virtual environment, the `PATH` variable would look something like this:
//// tab | Linux, macOS
```plaintext
/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin
```
That means that the system will now start looking first for programs in:
```plaintext
/home/user/code/awesome-project/.venv/bin
```
before looking in the other directories.
So, when you type `python` in the terminal, the system will find the Python program in
```plaintext
/home/user/code/awesome-project/.venv/bin/python
```
and use that one.
////
//// tab | Windows
```plaintext
C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32
```
That means that the system will now start looking first for programs in:
```plaintext
C:\Users\user\code\awesome-project\.venv\Scripts
```
before looking in the other directories.
So, when you type `python` in the terminal, the system will find the Python program in
```plaintext
C:\Users\user\code\awesome-project\.venv\Scripts\python
```
and use that one.
////
An important detail is that it will put the virtual environment path at the **beginning** of the `PATH` variable. The system will find it **before** finding any other Python available. This way, when you run `python`, it will use the Python **from the virtual environment** instead of any other `python` (for example, a `python` from a global environment).
Activating a virtual environment also changes a couple of other things, but this is one of the most important things it does.
## Checking a Virtual Environment { #checking-a-virtual-environment }
When you check if a virtual environment is active, for example with:
//// tab | Linux, macOS, Windows Bash
<div class="termy">
```console
$ which python
/home/user/code/awesome-project/.venv/bin/python
```
</div>
////
//// tab | Windows PowerShell
<div class="termy">
```console
$ Get-Command python
C:\Users\user\code\awesome-project\.venv\Scripts\python
```
</div>
////
That means that the `python` program that will be used is the one **in the virtual environment**.
You use `which` in Linux and macOS and `Get-Command` in Windows PowerShell.
The way that command works is that it will go and check in the `PATH` environment variable, going through **each path in order**, looking for the program called `python`. Once it finds it, it will **show you the path** to that program.
The most important part is that when you call `python`, that is the exact "`python`" that will be executed.
So, you can confirm if you are in the correct virtual environment.
/// tip
It's easy to activate one virtual environment, get one Python, and then **go to another project**.
And the second project **wouldn't work** because you are using the **incorrect Python**, from a virtual environment for another project.
It's useful being able to check what `python` is being used. 🤓
///
## Why Deactivate a Virtual Environment { #why-deactivate-a-virtual-environment }
For example, you could be working on a project `philosophers-stone`, **activate that virtual environment**, install packages and work with that environment.
And then you want to work on **another project** `prisoner-of-azkaban`.
You go to that project:
<div class="termy">
```console
$ cd ~/code/prisoner-of-azkaban
```
</div>
If you don't deactivate the virtual environment for `philosophers-stone`, when you run `python` in the terminal, it will try to use the Python from `philosophers-stone`.
<div class="termy">
```console
$ cd ~/code/prisoner-of-azkaban
$ python main.py
// Error importing sirius, it's not installed 😱
Traceback (most recent call last):
File "main.py", line 1, in <module>
import sirius
```
</div>
But if you deactivate the virtual environment and activate the new one for `prisoner-of-azkaban` then when you run `python` it will use the Python from the virtual environment in `prisoner-of-azkaban`.
<div class="termy">
```console
$ cd ~/code/prisoner-of-azkaban
// You don't need to be in the old directory to deactivate, you can do it wherever you are, even after going to the other project 😎
$ deactivate
// Activate the virtual environment in prisoner-of-azkaban/.venv 🚀
$ source .venv/bin/activate
// Now when you run python, it will find the package sirius installed in this virtual environment ✨
$ python main.py
I solemnly swear 🐺
```
</div>
## Alternatives { #alternatives }
This is a simple guide to get you started and teach you how everything works **underneath**.
There are many **alternatives** to managing virtual environments, package dependencies (requirements), projects.
Once you are ready and want to use a tool to **manage the entire project**, package dependencies, virtual environments, etc. I would suggest you try [uv](https://github.com/astral-sh/uv).
`uv` can do a lot of things, it can:
* **Install Python** for you, including different versions
* Manage the **virtual environment** for your projects
* Install **packages**
* Manage package **dependencies and versions** for your project
* Make sure you have an **exact** set of packages and versions to install, including their dependencies, so that you can be sure that you can run your project in production exactly the same as in your computer while developing, this is called **locking**
* And many other things
## Conclusion { #conclusion }
If you read and understood all this, now **you know much more** about virtual environments than many developers out there. 🤓
Knowing these details will most probably be useful in a future time when you are debugging something that seems complex, but you will know **how it all works underneath**. 😎
Read the [Virtual Environments guide](https://tiangolo.com/guides/virtual-environments/) to learn how virtual environments work underneath, including activation and the alternative `python -m venv` and `pip` workflow.
+2 -4
View File
@@ -81,8 +81,6 @@ nav:
- learn/index.md
- python-types.md
- async.md
- environment-variables.md
- virtual-environments.md
- "":
- tutorial/index.md
- tutorial/first-steps.md
@@ -223,9 +221,9 @@ nav:
- reference/staticfiles.md
- reference/templating.md
- reference/testclient.md
- fastapi-people.md
- "":
- resources/index.md
- fastapi-people.md
- help-fastapi.md
- contributing.md
- translations.md
@@ -288,7 +286,7 @@ extra:
- icon: octicons/mark-github-24
link: https://github.com/fastapi/fastapi
- icon: fontawesome/brands/discord
link: https://discord.gg/VQjSZaeJmf
link: https://discord.com/invite/VQjSZaeJmf
- icon: fontawesome/brands/x-twitter
link: https://x.com/fastapi
- icon: fontawesome/brands/bluesky
+1 -1
View File
@@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
__version__ = "0.139.2"
__version__ = "0.140.4"
from starlette import status as status
+2 -2
View File
@@ -66,7 +66,7 @@ class FastAPI(Starlette):
errors.
Read more in the
[Starlette docs for Applications](https://www.starlette.dev/applications/#instantiating-the-application).
[Starlette docs for Applications](https://starlette.dev/applications/#starlette.applications.Starlette).
"""
),
] = False,
@@ -960,7 +960,7 @@ class FastAPI(Starlette):
This is simply inherited from Starlette.
Read more about it in the
[Starlette docs for Applications](https://www.starlette.dev/applications/#storing-state-on-the-app-instance).
[Starlette docs for Applications](https://starlette.dev/applications/#storing-state-on-the-app-instance).
"""
),
] = State()
+175 -130
View File
@@ -2,7 +2,7 @@ import inspect
import sys
from collections.abc import Callable
from dataclasses import dataclass, field
from functools import cached_property, partial
from functools import lru_cache, partial
from typing import Any, Literal
from fastapi._compat import ModelField
@@ -28,7 +28,7 @@ def _impartial(func: Callable[..., Any]) -> Callable[..., Any]:
return func
@dataclass
@dataclass(slots=True)
class Dependant:
path_params: list[ModelField] = field(default_factory=list)
query_params: list[ModelField] = field(default_factory=list)
@@ -50,144 +50,189 @@ class Dependant:
path: str | None = None
scope: Literal["function", "request"] | None = None
@cached_property
def oauth_scopes(self) -> list[str]:
scopes = self.parent_oauth_scopes.copy() if self.parent_oauth_scopes else []
# This doesn't use a set to preserve order, just in case
for scope in self.own_oauth_scopes or []:
if scope not in scopes:
scopes.append(scope)
return scopes
@cached_property
def cache_key(self) -> DependencyCacheKey:
scopes_for_cache = (
tuple(sorted(set(self.oauth_scopes or []))) if self._uses_scopes else ()
)
return (
self.call,
scopes_for_cache,
self.computed_scope or "",
_UsesScopesCache = dict[int, tuple[Dependant, bool]]
_CALLABLE_CLASSIFICATION_CACHE_SIZE = 4096
class _CallIdentity:
__slots__ = ("call",)
def __init__(self, call: Callable[..., Any]) -> None:
self.call = call
def __hash__(self) -> int:
return id(self.call)
def __eq__(self, other: object) -> bool:
return isinstance(other, _CallIdentity) and self.call is other.call
def _get_oauth_scopes(*, dependant: Dependant) -> list[str]:
scopes = (
dependant.parent_oauth_scopes.copy() if dependant.parent_oauth_scopes else []
)
# This doesn't use a set to preserve order, just in case
for scope in dependant.own_oauth_scopes or []:
if scope not in scopes:
scopes.append(scope)
return scopes
def _get_cache_key(
*,
dependant: Dependant,
uses_scopes_cache: _UsesScopesCache | None = None,
) -> DependencyCacheKey:
scopes_for_cache = (
tuple(sorted(set(_get_oauth_scopes(dependant=dependant))))
if _uses_scopes(dependant=dependant, cache=uses_scopes_cache)
else ()
)
return (
dependant.call,
scopes_for_cache,
_get_computed_scope(dependant=dependant) or "",
)
def _uses_scopes(
*, dependant: Dependant, cache: _UsesScopesCache | None = None
) -> bool:
if cache is None:
cache = {}
cache_key = id(dependant)
cached = cache.get(cache_key)
if cached is not None and cached[0] is dependant:
return cached[1]
if dependant.own_oauth_scopes:
result = True
elif dependant.security_scopes_param_name is not None:
result = True
elif _is_security_scheme(dependant=dependant):
result = True
else:
result = any(
_uses_scopes(dependant=sub_dep, cache=cache)
for sub_dep in dependant.dependencies
)
cache[cache_key] = (dependant, result)
return result
@cached_property
def _uses_scopes(self) -> bool:
if self.own_oauth_scopes:
return True
if self.security_scopes_param_name is not None:
return True
if self._is_security_scheme:
return True
for sub_dep in self.dependencies:
if sub_dep._uses_scopes:
return True
def _is_security_scheme(*, dependant: Dependant) -> bool:
if dependant.call is None:
return False # pragma: no cover
unwrapped = _unwrapped_call(dependant.call)
return isinstance(unwrapped, SecurityBase)
def _get_security_scheme(*, dependant: Dependant) -> SecurityBase:
# Mainly to get the type of SecurityBase, but it's the same dependant.call
unwrapped = _unwrapped_call(dependant.call)
assert isinstance(unwrapped, SecurityBase)
return unwrapped
def _get_security_dependencies(*, dependant: Dependant) -> list[Dependant]:
return [dep for dep in dependant.dependencies if _is_security_scheme(dependant=dep)]
@lru_cache(maxsize=_CALLABLE_CLASSIFICATION_CACHE_SIZE)
def _is_gen_callable_cached(call_identity: _CallIdentity) -> bool:
call = call_identity.call
if inspect.isgeneratorfunction(_impartial(call)) or inspect.isgeneratorfunction(
_unwrapped_call(call)
):
return True
if inspect.isclass(_unwrapped_call(call)):
return False
dunder_call = getattr(_impartial(call), "__call__", None) # noqa: B004
if dunder_call is None:
return False # pragma: no cover
if inspect.isgeneratorfunction(
_impartial(dunder_call)
) or inspect.isgeneratorfunction(_unwrapped_call(dunder_call)):
return True
dunder_unwrapped_call = getattr(_unwrapped_call(call), "__call__", None) # noqa: B004
if dunder_unwrapped_call is None:
return False # pragma: no cover
return inspect.isgeneratorfunction(
_impartial(dunder_unwrapped_call)
) or inspect.isgeneratorfunction(_unwrapped_call(dunder_unwrapped_call))
@cached_property
def _is_security_scheme(self) -> bool:
if self.call is None:
return False # pragma: no cover
unwrapped = _unwrapped_call(self.call)
return isinstance(unwrapped, SecurityBase)
# Mainly to get the type of SecurityBase, but it's the same self.call
@cached_property
def _security_scheme(self) -> SecurityBase:
unwrapped = _unwrapped_call(self.call)
assert isinstance(unwrapped, SecurityBase)
return unwrapped
def _is_gen_callable(call: Callable[..., Any] | None) -> bool:
if call is None:
return False # pragma: no cover
return _is_gen_callable_cached(_CallIdentity(call))
@cached_property
def _security_dependencies(self) -> list["Dependant"]:
security_deps = [dep for dep in self.dependencies if dep._is_security_scheme]
return security_deps
@cached_property
def is_gen_callable(self) -> bool:
if self.call is None:
return False # pragma: no cover
if inspect.isgeneratorfunction(
_impartial(self.call)
) or inspect.isgeneratorfunction(_unwrapped_call(self.call)):
return True
if inspect.isclass(_unwrapped_call(self.call)):
return False
dunder_call = getattr(_impartial(self.call), "__call__", None) # noqa: B004
if dunder_call is None:
return False # pragma: no cover
if inspect.isgeneratorfunction(
_impartial(dunder_call)
) or inspect.isgeneratorfunction(_unwrapped_call(dunder_call)):
return True
dunder_unwrapped_call = getattr(_unwrapped_call(self.call), "__call__", None) # noqa: B004
if dunder_unwrapped_call is None:
return False # pragma: no cover
if inspect.isgeneratorfunction(
_impartial(dunder_unwrapped_call)
) or inspect.isgeneratorfunction(_unwrapped_call(dunder_unwrapped_call)):
return True
@lru_cache(maxsize=_CALLABLE_CLASSIFICATION_CACHE_SIZE)
def _is_async_gen_callable_cached(call_identity: _CallIdentity) -> bool:
call = call_identity.call
if inspect.isasyncgenfunction(_impartial(call)) or inspect.isasyncgenfunction(
_unwrapped_call(call)
):
return True
if inspect.isclass(_unwrapped_call(call)):
return False
dunder_call = getattr(_impartial(call), "__call__", None) # noqa: B004
if dunder_call is None:
return False # pragma: no cover
if inspect.isasyncgenfunction(
_impartial(dunder_call)
) or inspect.isasyncgenfunction(_unwrapped_call(dunder_call)):
return True
dunder_unwrapped_call = getattr(_unwrapped_call(call), "__call__", None) # noqa: B004
if dunder_unwrapped_call is None:
return False # pragma: no cover
return inspect.isasyncgenfunction(
_impartial(dunder_unwrapped_call)
) or inspect.isasyncgenfunction(_unwrapped_call(dunder_unwrapped_call))
@cached_property
def is_async_gen_callable(self) -> bool:
if self.call is None:
return False # pragma: no cover
if inspect.isasyncgenfunction(
_impartial(self.call)
) or inspect.isasyncgenfunction(_unwrapped_call(self.call)):
return True
if inspect.isclass(_unwrapped_call(self.call)):
return False
dunder_call = getattr(_impartial(self.call), "__call__", None) # noqa: B004
if dunder_call is None:
return False # pragma: no cover
if inspect.isasyncgenfunction(
_impartial(dunder_call)
) or inspect.isasyncgenfunction(_unwrapped_call(dunder_call)):
return True
dunder_unwrapped_call = getattr(_unwrapped_call(self.call), "__call__", None) # noqa: B004
if dunder_unwrapped_call is None:
return False # pragma: no cover
if inspect.isasyncgenfunction(
_impartial(dunder_unwrapped_call)
) or inspect.isasyncgenfunction(_unwrapped_call(dunder_unwrapped_call)):
return True
def _is_async_gen_callable(call: Callable[..., Any] | None) -> bool:
if call is None:
return False # pragma: no cover
return _is_async_gen_callable_cached(_CallIdentity(call))
@lru_cache(maxsize=_CALLABLE_CLASSIFICATION_CACHE_SIZE)
def _is_coroutine_callable_cached(call_identity: _CallIdentity) -> bool:
call = call_identity.call
if inspect.isroutine(_impartial(call)) and iscoroutinefunction(_impartial(call)):
return True
if inspect.isroutine(_unwrapped_call(call)) and iscoroutinefunction(
_unwrapped_call(call)
):
return True
if inspect.isclass(_unwrapped_call(call)):
return False
dunder_call = getattr(_impartial(call), "__call__", None) # noqa: B004
if dunder_call is None:
return False # pragma: no cover
if iscoroutinefunction(_impartial(dunder_call)) or iscoroutinefunction(
_unwrapped_call(dunder_call)
):
return True
dunder_unwrapped_call = getattr(_unwrapped_call(call), "__call__", None) # noqa: B004
if dunder_unwrapped_call is None:
return False # pragma: no cover
return iscoroutinefunction(
_impartial(dunder_unwrapped_call)
) or iscoroutinefunction(_unwrapped_call(dunder_unwrapped_call))
@cached_property
def is_coroutine_callable(self) -> bool:
if self.call is None:
return False # pragma: no cover
if inspect.isroutine(_impartial(self.call)) and iscoroutinefunction(
_impartial(self.call)
):
return True
if inspect.isroutine(_unwrapped_call(self.call)) and iscoroutinefunction(
_unwrapped_call(self.call)
):
return True
if inspect.isclass(_unwrapped_call(self.call)):
return False
dunder_call = getattr(_impartial(self.call), "__call__", None) # noqa: B004
if dunder_call is None:
return False # pragma: no cover
if iscoroutinefunction(_impartial(dunder_call)) or iscoroutinefunction(
_unwrapped_call(dunder_call)
):
return True
dunder_unwrapped_call = getattr(_unwrapped_call(self.call), "__call__", None) # noqa: B004
if dunder_unwrapped_call is None:
return False # pragma: no cover
if iscoroutinefunction(
_impartial(dunder_unwrapped_call)
) or iscoroutinefunction(_unwrapped_call(dunder_unwrapped_call)):
return True
return False
@cached_property
def computed_scope(self) -> str | None:
if self.scope:
return self.scope
if self.is_gen_callable or self.is_async_gen_callable:
return "request"
return None
def _is_coroutine_callable(call: Callable[..., Any] | None) -> bool:
if call is None:
return False # pragma: no cover
return _is_coroutine_callable_cached(_CallIdentity(call))
def _get_computed_scope(*, dependant: Dependant) -> str | None:
if dependant.scope:
return dependant.scope
if _is_gen_callable(dependant.call) or _is_async_gen_callable(dependant.call):
return "request"
return None
+62 -21
View File
@@ -54,7 +54,16 @@ from fastapi.concurrency import (
asynccontextmanager,
contextmanager_in_threadpool,
)
from fastapi.dependencies.models import Dependant
from fastapi.dependencies.models import (
Dependant,
_get_cache_key,
_get_computed_scope,
_get_oauth_scopes,
_is_async_gen_callable,
_is_coroutine_callable,
_is_gen_callable,
_UsesScopesCache,
)
from fastapi.exceptions import DependencyScopeError
from fastapi.logger import logger
from fastapi.security.oauth2 import SecurityScopes
@@ -141,12 +150,24 @@ def get_flat_dependant(
skip_repeats: bool = False,
visited: list[DependencyCacheKey] | None = None,
parent_oauth_scopes: list[str] | None = None,
_uses_scopes_cache: _UsesScopesCache | None = None,
) -> Dependant:
if visited is None:
visited = []
visited.append(dependant.cache_key)
track_visited = (
skip_repeats or visited is not None or _uses_scopes_cache is not None
)
if track_visited:
if visited is None:
visited = []
if _uses_scopes_cache is None:
_uses_scopes_cache = {}
visited.append(
_get_cache_key(
dependant=dependant,
uses_scopes_cache=_uses_scopes_cache,
)
)
use_parent_oauth_scopes = (parent_oauth_scopes or []) + (
dependant.oauth_scopes or []
_get_oauth_scopes(dependant=dependant)
)
flat_dependant = Dependant(
@@ -170,13 +191,22 @@ def get_flat_dependant(
scope=dependant.scope,
)
for sub_dependant in dependant.dependencies:
if skip_repeats and sub_dependant.cache_key in visited:
continue
if skip_repeats:
assert visited is not None
if (
_get_cache_key(
dependant=sub_dependant,
uses_scopes_cache=_uses_scopes_cache,
)
in visited
):
continue
flat_sub = get_flat_dependant(
sub_dependant,
skip_repeats=skip_repeats,
visited=visited,
parent_oauth_scopes=flat_dependant.oauth_scopes,
parent_oauth_scopes=_get_oauth_scopes(dependant=flat_dependant),
_uses_scopes_cache=_uses_scopes_cache,
)
flat_dependant.dependencies.append(flat_sub)
flat_dependant.path_params.extend(flat_sub.path_params)
@@ -317,8 +347,11 @@ def get_dependant(
if param_details.depends is not None:
assert param_details.depends.dependency
if (
(dependant.is_gen_callable or dependant.is_async_gen_callable)
and dependant.computed_scope == "request"
(
_is_gen_callable(dependant.call)
or _is_async_gen_callable(dependant.call)
)
and _get_computed_scope(dependant=dependant) == "request"
and param_details.depends.scope == "function"
):
assert dependant.call
@@ -579,9 +612,9 @@ async def _solve_generator(
*, dependant: Dependant, stack: AsyncExitStack, sub_values: dict[str, Any]
) -> Any:
assert dependant.call
if dependant.is_async_gen_callable:
if _is_async_gen_callable(dependant.call):
cm = asynccontextmanager(dependant.call)(**sub_values)
elif dependant.is_gen_callable:
elif _is_gen_callable(dependant.call):
cm = contextmanager_in_threadpool(contextmanager(dependant.call)(**sub_values))
return await stack.enter_async_context(cm)
@@ -608,6 +641,7 @@ async def solve_dependencies(
# people might be monkey patching this function (although that's not supported)
async_exit_stack: AsyncExitStack,
embed_body_fields: bool,
_uses_scopes_cache: _UsesScopesCache | None = None,
) -> SolvedDependency:
request_astack = request.scope.get("fastapi_inner_astack")
assert isinstance(request_astack, AsyncExitStack), (
@@ -625,6 +659,8 @@ async def solve_dependencies(
response.status_code = None # type: ignore
if dependency_cache is None:
dependency_cache = {}
if _uses_scopes_cache is None:
_uses_scopes_cache = {}
for sub_dependant in dependant.dependencies:
sub_dependant.call = cast(Callable[..., Any], sub_dependant.call)
call = sub_dependant.call
@@ -642,7 +678,7 @@ async def solve_dependencies(
path=use_path,
call=call,
name=sub_dependant.name,
parent_oauth_scopes=sub_dependant.oauth_scopes,
parent_oauth_scopes=_get_oauth_scopes(dependant=sub_dependant),
scope=sub_dependant.scope,
)
@@ -656,15 +692,20 @@ async def solve_dependencies(
dependency_cache=dependency_cache,
async_exit_stack=async_exit_stack,
embed_body_fields=embed_body_fields,
_uses_scopes_cache=_uses_scopes_cache,
)
background_tasks = solved_result.background_tasks
if solved_result.errors:
errors.extend(solved_result.errors)
continue
if sub_dependant.use_cache and sub_dependant.cache_key in dependency_cache:
solved = dependency_cache[sub_dependant.cache_key]
elif (
use_sub_dependant.is_gen_callable or use_sub_dependant.is_async_gen_callable
sub_dependant_cache_key = _get_cache_key(
dependant=sub_dependant,
uses_scopes_cache=_uses_scopes_cache,
)
if sub_dependant.use_cache and sub_dependant_cache_key in dependency_cache:
solved = dependency_cache[sub_dependant_cache_key]
elif _is_gen_callable(use_sub_dependant.call) or _is_async_gen_callable(
use_sub_dependant.call
):
use_astack = request_astack
if sub_dependant.scope == "function":
@@ -674,14 +715,14 @@ async def solve_dependencies(
stack=use_astack,
sub_values=solved_result.values,
)
elif use_sub_dependant.is_coroutine_callable:
elif _is_coroutine_callable(use_sub_dependant.call):
solved = await call(**solved_result.values)
else:
solved = await run_in_threadpool(call, **solved_result.values)
if sub_dependant.name is not None:
values[sub_dependant.name] = solved
if sub_dependant.cache_key not in dependency_cache:
dependency_cache[sub_dependant.cache_key] = solved
if sub_dependant_cache_key not in dependency_cache:
dependency_cache[sub_dependant_cache_key] = solved
path_values, path_errors = request_params_to_args(
dependant.path_params, request.path_params
)
@@ -724,7 +765,7 @@ async def solve_dependencies(
values[dependant.response_param_name] = response
if dependant.security_scopes_param_name:
values[dependant.security_scopes_param_name] = SecurityScopes(
scopes=dependant.oauth_scopes
scopes=_get_oauth_scopes(dependant=dependant)
)
return SolvedDependency(
values=values,
+24 -10
View File
@@ -15,7 +15,12 @@ from fastapi._compat import (
lenient_issubclass,
)
from fastapi.datastructures import DefaultPlaceholder, _Unset
from fastapi.dependencies.models import Dependant
from fastapi.dependencies.models import (
Dependant,
_get_oauth_scopes,
_get_security_dependencies,
_get_security_scheme,
)
from fastapi.dependencies.utils import (
_get_flat_fields_from_params,
get_flat_dependant,
@@ -84,18 +89,19 @@ def get_openapi_security_definitions(
security_definitions = {}
# Use a dict to merge scopes for same security scheme
operation_security_dict: dict[str, list[str]] = {}
for security_dependency in flat_dependant._security_dependencies:
for security_dependency in _get_security_dependencies(dependant=flat_dependant):
security_scheme = _get_security_scheme(dependant=security_dependency)
security_definition = jsonable_encoder(
security_dependency._security_scheme.model,
security_scheme.model,
by_alias=True,
exclude_none=True,
)
security_name = security_dependency._security_scheme.scheme_name
security_name = security_scheme.scheme_name
security_definitions[security_name] = security_definition
# Merge scopes for the same security scheme
if security_name not in operation_security_dict:
operation_security_dict[security_name] = []
for scope in security_dependency.oauth_scopes or []:
for scope in _get_oauth_scopes(dependant=security_dependency):
if scope not in operation_security_dict[security_name]:
operation_security_dict[security_name].append(scope)
operation_security = [
@@ -106,7 +112,7 @@ def get_openapi_security_definitions(
def _get_openapi_operation_parameters(
*,
dependant: Dependant,
flat_dependant: Dependant,
model_name_map: ModelNameMap,
field_mapping: dict[
tuple[ModelField, Literal["validation", "serialization"]], dict[str, Any]
@@ -114,7 +120,6 @@ def _get_openapi_operation_parameters(
separate_input_output_schemas: bool = True,
) -> list[dict[str, Any]]:
parameters = []
flat_dependant = get_flat_dependant(dependant, skip_repeats=True)
path_params = _get_flat_fields_from_params(flat_dependant.path_params)
query_params = _get_flat_fields_from_params(flat_dependant.query_params)
header_params = _get_flat_fields_from_params(flat_dependant.header_params)
@@ -278,12 +283,22 @@ def get_openapi_path(
assert current_response_class, "A response class is needed to generate OpenAPI"
route_response_media_type: str | None = current_response_class.media_type
if route.include_in_schema:
flat_dependant = get_flat_dependant(route.dependant, skip_repeats=True)
all_route_params = [
field
for fields in (
flat_dependant.path_params,
flat_dependant.query_params,
flat_dependant.header_params,
flat_dependant.cookie_params,
)
for field in _get_flat_fields_from_params(fields)
]
for method in route.methods:
operation = get_openapi_operation_metadata(
route=route, method=method, operation_ids=operation_ids
)
parameters: list[dict[str, Any]] = []
flat_dependant = get_flat_dependant(route.dependant, skip_repeats=True)
security_definitions, operation_security = get_openapi_security_definitions(
flat_dependant=flat_dependant
)
@@ -292,7 +307,7 @@ def get_openapi_path(
if security_definitions:
security_schemes.update(security_definitions)
operation_parameters = _get_openapi_operation_parameters(
dependant=route.dependant,
flat_dependant=flat_dependant,
model_name_map=model_name_map,
field_mapping=field_mapping,
separate_input_output_schemas=separate_input_output_schemas,
@@ -452,7 +467,6 @@ def get_openapi_path(
deep_dict_update(openapi_response, process_response)
openapi_response["description"] = description
http422 = "422"
all_route_params = get_flat_params(route.dependant)
if (all_route_params or route.body_field) and not any(
status in operation["responses"]
for status in [http422, "4XX", "default"]
+20 -16
View File
@@ -48,7 +48,12 @@ from fastapi._compat import (
lenient_issubclass,
)
from fastapi.datastructures import Default, DefaultPlaceholder
from fastapi.dependencies.models import Dependant
from fastapi.dependencies.models import (
Dependant,
_is_async_gen_callable,
_is_coroutine_callable,
_is_gen_callable,
)
from fastapi.dependencies.utils import (
_should_embed_body_fields,
get_body_field,
@@ -384,7 +389,7 @@ def get_request_handler(
is_json_stream: bool = False,
) -> Callable[[Request], Coroutine[Any, Any, Response]]:
assert dependant.call is not None, "dependant.call must be a function"
is_coroutine = dependant.is_coroutine_callable
is_coroutine = _is_coroutine_callable(dependant.call)
is_body_form = body_field and isinstance(body_field.field_info, params.Form)
if isinstance(response_class, DefaultPlaceholder):
actual_response_class: type[Response] = response_class.value
@@ -543,7 +548,7 @@ def get_request_handler(
data_str=_serialize_data(item).decode("utf-8")
)
if dependant.is_async_gen_callable:
if _is_async_gen_callable(dependant.call):
sse_aiter: AsyncIterator[Any] = gen.__aiter__()
else:
sse_aiter = iterate_in_threadpool(gen)
@@ -641,7 +646,7 @@ def get_request_handler(
def _serialize_item(item: Any) -> bytes:
return _serialize_data(item) + b"\n"
if dependant.is_async_gen_callable:
if _is_async_gen_callable(dependant.call):
async def _async_stream_jsonl() -> AsyncIterator[bytes]:
async for item in gen:
@@ -667,10 +672,12 @@ def get_request_handler(
background=solved_result.background_tasks,
)
response.headers.raw.extend(solved_result.response.headers.raw)
elif dependant.is_async_gen_callable or dependant.is_gen_callable:
elif _is_async_gen_callable(dependant.call) or _is_gen_callable(
dependant.call
):
# Raw streaming with explicit response_class (e.g. StreamingResponse)
gen = dependant.call(**solved_result.values)
if dependant.is_async_gen_callable:
if _is_async_gen_callable(dependant.call):
async def _async_stream_raw(
async_gen: AsyncIterator[Any],
@@ -800,7 +807,7 @@ class APIWebSocketRoute(routing.WebSocketRoute):
self.path_regex, self.path_format, self.param_convertors = compile_path(path)
(
self.dependant,
self._flat_dependant,
_,
self._embed_body_fields,
) = _build_dependant_with_parameterless_dependencies(
path=self.path_format,
@@ -937,7 +944,6 @@ class _APIRouteLike(Protocol):
description: str
response_fields: dict[int | str, ModelField]
dependant: Dependant
_flat_dependant: Dependant
_embed_body_fields: bool
body_field: ModelField | None
is_sse_stream: bool
@@ -1084,7 +1090,7 @@ def _populate_api_route_state(
assert callable(endpoint), "An endpoint must be a callable"
(
route.dependant,
route._flat_dependant,
flat_dependant,
route._embed_body_fields,
) = _build_dependant_with_parameterless_dependencies(
path=route.path_format,
@@ -1092,13 +1098,13 @@ def _populate_api_route_state(
dependencies=route.dependencies,
)
route.body_field = get_body_field(
flat_dependant=route._flat_dependant,
flat_dependant=flat_dependant,
name=route.unique_id,
embed_body_fields=route._embed_body_fields,
)
# Detect generator endpoints that should stream as JSONL or SSE
is_generator = (
route.dependant.is_async_gen_callable or route.dependant.is_gen_callable
is_generator = _is_async_gen_callable(route.dependant.call) or _is_gen_callable(
route.dependant.call
)
route.is_sse_stream = is_generator and lenient_issubclass(
response_class, EventSourceResponse
@@ -1138,7 +1144,6 @@ class APIRoute(routing.Route):
description: str
response_fields: dict[int | str, ModelField]
dependant: Dependant
_flat_dependant: Dependant
_embed_body_fields: bool
body_field: ModelField | None
is_sse_stream: bool
@@ -1403,7 +1408,6 @@ class _EffectiveRouteContext:
description: str = ""
response_fields: dict[int | str, ModelField] = field(default_factory=dict)
dependant: Dependant | None = None
_flat_dependant: Dependant | None = None
_embed_body_fields: bool = False
body_field: ModelField | None = None
is_sse_stream: bool = False
@@ -1479,7 +1483,7 @@ class _EffectiveRouteContext:
)
(
context.dependant,
context._flat_dependant,
_,
context._embed_body_fields,
) = _build_dependant_with_parameterless_dependencies(
path="",
@@ -2073,7 +2077,7 @@ class _FrontendRouteGroup(BaseRoute):
self.dependency_overrides_provider = dependency_overrides_provider
(
self.dependant,
self._flat_dependant,
_,
self._embed_body_fields,
) = _build_dependant_with_parameterless_dependencies(
path="",
+2 -1
View File
@@ -168,7 +168,7 @@ tests = [
"pwdlib[argon2] >=0.2.1",
"pyjwt >=2.9.0",
"pytest >=9.0.0",
"pytest-codspeed >=4.2.0",
"pytest-codspeed >=4.3.0",
"pyyaml >=5.3.1,<7.0.0",
"sqlmodel >=0.0.31",
"strawberry-graphql >=0.200.0,<1.0.0",
@@ -244,6 +244,7 @@ relative_files = true
context = '${CONTEXT}'
omit = [
"tests/benchmarks/*",
"tests/memory_benchmarks/*",
"docs_src/response_model/tutorial003_04_py310.py",
"docs_src/dependencies/tutorial013_an_py310.py", # temporary code example?
"docs_src/dependencies/tutorial014_an_py310.py", # temporary code example?
-316
View File
@@ -1,316 +0,0 @@
import logging
import secrets
import subprocess
from collections import Counter
from datetime import datetime
from pathlib import Path
from typing import Any
import httpx
import yaml
from github import Github
from pydantic import BaseModel, SecretStr
from pydantic_settings import BaseSettings
github_graphql_url = "https://api.github.com/graphql"
prs_query = """
query Q($after: String) {
repository(name: "fastapi", owner: "fastapi") {
pullRequests(first: 100, after: $after) {
edges {
cursor
node {
number
labels(first: 100) {
nodes {
name
}
}
author {
login
avatarUrl
url
}
title
createdAt
lastEditedAt
updatedAt
state
reviews(first:100) {
nodes {
author {
login
avatarUrl
url
}
state
}
}
}
}
}
}
}
"""
class Author(BaseModel):
login: str
avatarUrl: str
url: str
class LabelNode(BaseModel):
name: str
class Labels(BaseModel):
nodes: list[LabelNode]
class ReviewNode(BaseModel):
author: Author | None = None
state: str
class Reviews(BaseModel):
nodes: list[ReviewNode]
class PullRequestNode(BaseModel):
number: int
labels: Labels
author: Author | None = None
title: str
createdAt: datetime
lastEditedAt: datetime | None = None
updatedAt: datetime | None = None
state: str
reviews: Reviews
class PullRequestEdge(BaseModel):
cursor: str
node: PullRequestNode
class PullRequests(BaseModel):
edges: list[PullRequestEdge]
class PRsRepository(BaseModel):
pullRequests: PullRequests
class PRsResponseData(BaseModel):
repository: PRsRepository
class PRsResponse(BaseModel):
data: PRsResponseData
class Settings(BaseSettings):
github_token: SecretStr
github_repository: str
httpx_timeout: int = 30
def get_graphql_response(
*,
settings: Settings,
query: str,
after: str | None = None,
) -> dict[str, Any]:
headers = {"Authorization": f"token {settings.github_token.get_secret_value()}"}
variables = {"after": after}
response = httpx.post(
github_graphql_url,
headers=headers,
timeout=settings.httpx_timeout,
json={"query": query, "variables": variables, "operationName": "Q"},
)
if response.status_code != 200:
logging.error(f"Response was not 200, after: {after}")
logging.error(response.text)
raise RuntimeError(response.text)
data = response.json()
if "errors" in data:
logging.error(f"Errors in response, after: {after}")
logging.error(data["errors"])
logging.error(response.text)
raise RuntimeError(response.text)
return data
def get_graphql_pr_edges(
*, settings: Settings, after: str | None = None
) -> list[PullRequestEdge]:
data = get_graphql_response(settings=settings, query=prs_query, after=after)
graphql_response = PRsResponse.model_validate(data)
return graphql_response.data.repository.pullRequests.edges
def get_pr_nodes(settings: Settings) -> list[PullRequestNode]:
pr_nodes: list[PullRequestNode] = []
pr_edges = get_graphql_pr_edges(settings=settings)
while pr_edges:
for edge in pr_edges:
pr_nodes.append(edge.node)
last_edge = pr_edges[-1]
pr_edges = get_graphql_pr_edges(settings=settings, after=last_edge.cursor)
return pr_nodes
class ContributorsResults(BaseModel):
contributors: Counter[str]
translation_reviewers: Counter[str]
translators: Counter[str]
authors: dict[str, Author]
def get_contributors(pr_nodes: list[PullRequestNode]) -> ContributorsResults:
contributors = Counter[str]()
translation_reviewers = Counter[str]()
translators = Counter[str]()
authors: dict[str, Author] = {}
for pr in pr_nodes:
if pr.author:
authors[pr.author.login] = pr.author
is_lang = False
for label in pr.labels.nodes:
if label.name == "lang-all":
is_lang = True
break
for review in pr.reviews.nodes:
if review.author:
authors[review.author.login] = review.author
if is_lang:
translation_reviewers[review.author.login] += 1
if pr.state == "MERGED" and pr.author:
if is_lang:
translators[pr.author.login] += 1
else:
contributors[pr.author.login] += 1
return ContributorsResults(
contributors=contributors,
translation_reviewers=translation_reviewers,
translators=translators,
authors=authors,
)
def get_users_to_write(
*,
counter: Counter[str],
authors: dict[str, Author],
min_count: int = 2,
) -> dict[str, Any]:
users: dict[str, Any] = {}
for user, count in counter.most_common():
if count >= min_count:
author = authors[user]
users[user] = {
"login": user,
"count": count,
"avatarUrl": author.avatarUrl,
"url": author.url,
}
return users
def update_content(*, content_path: Path, new_content: Any) -> bool:
old_content = content_path.read_text(encoding="utf-8")
new_content = yaml.dump(new_content, sort_keys=False, width=200, allow_unicode=True)
if old_content == new_content:
logging.info(f"The content hasn't changed for {content_path}")
return False
content_path.write_text(new_content, encoding="utf-8")
logging.info(f"Updated {content_path}")
return True
def main() -> None:
logging.basicConfig(level=logging.INFO)
settings = Settings() # ty: ignore[missing-argument]
logging.info(f"Using config: {settings.model_dump_json()}")
g = Github(settings.github_token.get_secret_value())
repo = g.get_repo(settings.github_repository)
pr_nodes = get_pr_nodes(settings=settings)
contributors_results = get_contributors(pr_nodes=pr_nodes)
authors = contributors_results.authors
top_contributors = get_users_to_write(
counter=contributors_results.contributors,
authors=authors,
)
top_translators = get_users_to_write(
counter=contributors_results.translators,
authors=authors,
)
top_translations_reviewers = get_users_to_write(
counter=contributors_results.translation_reviewers,
authors=authors,
)
# For local development
# contributors_path = Path("../docs/en/data/contributors.yml")
contributors_path = Path("./docs/en/data/contributors.yml")
# translators_path = Path("../docs/en/data/translators.yml")
translators_path = Path("./docs/en/data/translators.yml")
# translation_reviewers_path = Path("../docs/en/data/translation_reviewers.yml")
translation_reviewers_path = Path("./docs/en/data/translation_reviewers.yml")
updated = [
update_content(content_path=contributors_path, new_content=top_contributors),
update_content(content_path=translators_path, new_content=top_translators),
update_content(
content_path=translation_reviewers_path,
new_content=top_translations_reviewers,
),
]
if not any(updated):
logging.info("The data hasn't changed, finishing.")
return
logging.info("Setting up GitHub Actions git user")
subprocess.run(["git", "config", "user.name", "github-actions[bot]"], check=True)
subprocess.run(
["git", "config", "user.email", "github-actions[bot]@users.noreply.github.com"],
check=True,
)
branch_name = f"fastapi-people-contributors-{secrets.token_hex(4)}"
logging.info(f"Creating a new branch {branch_name}")
subprocess.run(["git", "checkout", "-b", branch_name], check=True)
logging.info("Adding updated file")
subprocess.run(
[
"git",
"add",
str(contributors_path),
str(translators_path),
str(translation_reviewers_path),
],
check=True,
)
logging.info("Committing updated file")
message = "👥 Update FastAPI People - Contributors and Translators"
subprocess.run(["git", "commit", "-m", message], check=True)
logging.info("Pushing branch")
subprocess.run(["git", "push", "origin", branch_name], check=True)
logging.info("Creating PR")
pr = repo.create_pull(title=message, body=message, base="master", head=branch_name)
logging.info(f"Created PR: {pr.number}")
logging.info("Finished")
if __name__ == "__main__":
main()
-483
View File
@@ -1,483 +0,0 @@
import logging
import secrets
import subprocess
import time
from collections import Counter
from collections.abc import Container
from datetime import datetime, timedelta, timezone
from math import ceil
from pathlib import Path
from typing import Annotated, Any
import httpx
import yaml
from github import Github
from pydantic import BaseModel, BeforeValidator, SecretStr
from pydantic_settings import BaseSettings
github_graphql_url = "https://api.github.com/graphql"
questions_category_id = "DIC_kwDOCZduT84B6E2a"
POINTS_PER_MINUTE_LIMIT = 84 # 5000 points per hour
MINIMIZED_COMMENTS_REASONS_TO_EXCLUDE = {"abuse", "off-topic", "duplicate", "spam"}
class RateLimiter:
def __init__(self) -> None:
self.last_query_cost: int = 1
self.remaining_points: int = 5000
self.reset_at: datetime = datetime.fromtimestamp(0, timezone.utc)
self.last_request_start_time: datetime = datetime.fromtimestamp(0, timezone.utc)
self.speed_multiplier: float = 1.0
def __enter__(self) -> "RateLimiter":
now = datetime.now(tz=timezone.utc)
# Handle primary rate limits
primary_limit_wait_time = 0.0
if self.remaining_points <= self.last_query_cost:
primary_limit_wait_time = (self.reset_at - now).total_seconds() + 2
logging.warning(
f"Approaching GitHub API rate limit, remaining points: {self.remaining_points}, "
f"reset time in {primary_limit_wait_time} seconds"
)
# Handle secondary rate limits
secondary_limit_wait_time = 0.0
points_per_minute = POINTS_PER_MINUTE_LIMIT * self.speed_multiplier
interval = 60 / (points_per_minute / self.last_query_cost)
time_since_last_request = (now - self.last_request_start_time).total_seconds()
if time_since_last_request < interval:
secondary_limit_wait_time = interval - time_since_last_request
final_wait_time = ceil(max(primary_limit_wait_time, secondary_limit_wait_time))
logging.info(f"Sleeping for {final_wait_time} seconds to respect rate limit")
time.sleep(max(final_wait_time, 1))
self.last_request_start_time = datetime.now(tz=timezone.utc)
return self
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
pass
def update_request_info(self, cost: int, remaining: int, reset_at: str) -> None:
self.last_query_cost = cost
self.remaining_points = remaining
self.reset_at = datetime.fromisoformat(reset_at.replace("Z", "+00:00"))
rate_limiter = RateLimiter()
discussions_query = """
query Q($after: String, $category_id: ID) {
repository(name: "fastapi", owner: "fastapi") {
discussions(first: 30, after: $after, categoryId: $category_id) {
edges {
cursor
node {
number
author {
login
avatarUrl
url
}
createdAt
comments(first: 50) {
totalCount
nodes {
createdAt
author {
login
avatarUrl
url
}
isAnswer
replies(first: 10) {
totalCount
nodes {
createdAt
author {
login
avatarUrl
url
}
minimizedReason
}
}
minimizedReason
}
}
}
}
}
}
rateLimit {
cost
remaining
resetAt
}
}
"""
LowerStr = Annotated[
str, BeforeValidator(lambda v: v.lower() if isinstance(v, str) else v)
]
class Author(BaseModel):
login: str
avatarUrl: str | None = None
url: str | None = None
class CommentsNode(BaseModel):
createdAt: datetime
author: Author | None = None
minimizedReason: LowerStr | None = None
class Replies(BaseModel):
totalCount: int
nodes: list[CommentsNode]
class DiscussionsCommentsNode(CommentsNode):
minimizedReason: LowerStr | None = None
replies: Replies
class DiscussionsComments(BaseModel):
totalCount: int
nodes: list[DiscussionsCommentsNode]
class DiscussionsNode(BaseModel):
number: int
author: Author | None = None
title: str | None = None
createdAt: datetime
comments: DiscussionsComments
class DiscussionsEdge(BaseModel):
cursor: str
node: DiscussionsNode
class Discussions(BaseModel):
edges: list[DiscussionsEdge]
class DiscussionsRepository(BaseModel):
discussions: Discussions
class DiscussionsResponseData(BaseModel):
repository: DiscussionsRepository
class DiscussionsResponse(BaseModel):
data: DiscussionsResponseData
class Settings(BaseSettings):
github_token: SecretStr
github_repository: str
httpx_timeout: int = 30
speed_multiplier: float = 1.0
def get_graphql_response(
*,
settings: Settings,
query: str,
after: str | None = None,
category_id: str | None = None,
) -> dict[str, Any]:
headers = {"Authorization": f"token {settings.github_token.get_secret_value()}"}
variables = {"after": after, "category_id": category_id}
response = httpx.post(
github_graphql_url,
headers=headers,
timeout=settings.httpx_timeout,
json={"query": query, "variables": variables, "operationName": "Q"},
)
if response.status_code != 200:
logging.error(
f"Response was not 200, after: {after}, category_id: {category_id}"
)
logging.error(response.text)
raise RuntimeError(response.text)
data = response.json()
if "errors" in data:
logging.error(f"Errors in response, after: {after}, category_id: {category_id}")
logging.error(data["errors"])
logging.error(response.text)
raise RuntimeError(response.text)
return data
def get_graphql_question_discussion_edges(
*,
settings: Settings,
after: str | None = None,
) -> list[DiscussionsEdge]:
with rate_limiter:
data = get_graphql_response(
settings=settings,
query=discussions_query,
after=after,
category_id=questions_category_id,
)
rate_limiter.update_request_info(
cost=data["data"]["rateLimit"]["cost"],
remaining=data["data"]["rateLimit"]["remaining"],
reset_at=data["data"]["rateLimit"]["resetAt"],
)
graphql_response = DiscussionsResponse.model_validate(data)
return graphql_response.data.repository.discussions.edges
class DiscussionExpertsResults(BaseModel):
commenters: Counter[str]
last_month_commenters: Counter[str]
three_months_commenters: Counter[str]
six_months_commenters: Counter[str]
one_year_commenters: Counter[str]
authors: dict[str, Author]
def get_discussion_nodes(settings: Settings) -> list[DiscussionsNode]:
discussion_nodes: list[DiscussionsNode] = []
discussion_edges = get_graphql_question_discussion_edges(settings=settings)
while discussion_edges:
for discussion_edge in discussion_edges:
discussion_nodes.append(discussion_edge.node)
last_edge = discussion_edges[-1]
discussion_edges = get_graphql_question_discussion_edges(
settings=settings, after=last_edge.cursor
)
return discussion_nodes
def get_discussions_experts(
discussion_nodes: list[DiscussionsNode],
) -> DiscussionExpertsResults:
commenters = Counter[str]()
last_month_commenters = Counter[str]()
three_months_commenters = Counter[str]()
six_months_commenters = Counter[str]()
one_year_commenters = Counter[str]()
authors: dict[str, Author] = {}
now = datetime.now(tz=timezone.utc)
one_month_ago = now - timedelta(days=30)
three_months_ago = now - timedelta(days=90)
six_months_ago = now - timedelta(days=180)
one_year_ago = now - timedelta(days=365)
for discussion in discussion_nodes:
discussion_author_name = None
if discussion.author:
authors[discussion.author.login] = discussion.author
discussion_author_name = discussion.author.login
discussion_commentors: dict[str, datetime] = {}
for comment in discussion.comments.nodes:
if (
comment.minimizedReason not in MINIMIZED_COMMENTS_REASONS_TO_EXCLUDE
and comment.author
):
authors[comment.author.login] = comment.author
if comment.author.login != discussion_author_name:
author_time = discussion_commentors.get(
comment.author.login, comment.createdAt
)
discussion_commentors[comment.author.login] = max(
author_time, comment.createdAt
)
for reply in comment.replies.nodes:
if (
reply.minimizedReason not in MINIMIZED_COMMENTS_REASONS_TO_EXCLUDE
and reply.author
):
authors[reply.author.login] = reply.author
if reply.author.login != discussion_author_name:
author_time = discussion_commentors.get(
reply.author.login, reply.createdAt
)
discussion_commentors[reply.author.login] = max(
author_time, reply.createdAt
)
for author_name, author_time in discussion_commentors.items():
commenters[author_name] += 1
if author_time > one_month_ago:
last_month_commenters[author_name] += 1
if author_time > three_months_ago:
three_months_commenters[author_name] += 1
if author_time > six_months_ago:
six_months_commenters[author_name] += 1
if author_time > one_year_ago:
one_year_commenters[author_name] += 1
discussion_experts_results = DiscussionExpertsResults(
authors=authors,
commenters=commenters,
last_month_commenters=last_month_commenters,
three_months_commenters=three_months_commenters,
six_months_commenters=six_months_commenters,
one_year_commenters=one_year_commenters,
)
return discussion_experts_results
def get_top_users(
*,
counter: Counter[str],
authors: dict[str, Author],
skip_users: Container[str],
min_count: int = 2,
) -> list[dict[str, Any]]:
users: list[dict[str, Any]] = []
for commenter, count in counter.most_common(50):
if commenter in skip_users:
continue
if count >= min_count:
author = authors[commenter]
users.append(
{
"login": commenter,
"count": count,
"avatarUrl": author.avatarUrl,
"url": author.url,
}
)
return users
def get_users_to_write(
*,
counter: Counter[str],
authors: dict[str, Author],
min_count: int = 2,
) -> list[dict[str, Any]]:
users: dict[str, Any] = {}
users_list: list[dict[str, Any]] = []
for user, count in counter.most_common(60):
if count >= min_count:
author = authors[user]
user_data = {
"login": user,
"count": count,
"avatarUrl": author.avatarUrl,
"url": author.url,
}
users[user] = user_data
users_list.append(user_data)
return users_list
def update_content(*, content_path: Path, new_content: Any) -> bool:
old_content = content_path.read_text(encoding="utf-8")
new_content = yaml.dump(new_content, sort_keys=False, width=200, allow_unicode=True)
if old_content == new_content:
logging.info(f"The content hasn't changed for {content_path}")
return False
content_path.write_text(new_content, encoding="utf-8")
logging.info(f"Updated {content_path}")
return True
def main() -> None:
logging.basicConfig(level=logging.INFO)
settings = Settings() # ty: ignore[missing-argument]
logging.info(f"Using config: {settings.model_dump_json()}")
rate_limiter.speed_multiplier = settings.speed_multiplier
g = Github(settings.github_token.get_secret_value())
repo = g.get_repo(settings.github_repository)
discussion_nodes = get_discussion_nodes(settings=settings)
experts_results = get_discussions_experts(discussion_nodes=discussion_nodes)
authors = experts_results.authors
maintainers_logins = {"tiangolo"}
maintainers = []
for login in maintainers_logins:
user = authors[login]
maintainers.append(
{
"login": login,
"answers": experts_results.commenters[login],
"avatarUrl": user.avatarUrl,
"url": user.url,
}
)
experts = get_users_to_write(
counter=experts_results.commenters,
authors=authors,
)
last_month_experts = get_users_to_write(
counter=experts_results.last_month_commenters,
authors=authors,
)
three_months_experts = get_users_to_write(
counter=experts_results.three_months_commenters,
authors=authors,
)
six_months_experts = get_users_to_write(
counter=experts_results.six_months_commenters,
authors=authors,
)
one_year_experts = get_users_to_write(
counter=experts_results.one_year_commenters,
authors=authors,
)
people = {
"maintainers": maintainers,
"experts": experts,
"last_month_experts": last_month_experts,
"three_months_experts": three_months_experts,
"six_months_experts": six_months_experts,
"one_year_experts": one_year_experts,
}
# For local development
# people_path = Path("../docs/en/data/people.yml")
people_path = Path("./docs/en/data/people.yml")
updated = update_content(content_path=people_path, new_content=people)
if not updated:
logging.info("The data hasn't changed, finishing.")
return
logging.info("Setting up GitHub Actions git user")
subprocess.run(["git", "config", "user.name", "github-actions[bot]"], check=True)
subprocess.run(
["git", "config", "user.email", "github-actions[bot]@users.noreply.github.com"],
check=True,
)
branch_name = f"fastapi-people-experts-{secrets.token_hex(4)}"
logging.info(f"Creating a new branch {branch_name}")
subprocess.run(["git", "checkout", "-b", branch_name], check=True)
logging.info("Adding updated file")
subprocess.run(["git", "add", str(people_path)], check=True)
logging.info("Committing updated file")
message = "👥 Update FastAPI People - Experts"
subprocess.run(["git", "commit", "-m", message], check=True)
logging.info("Pushing branch")
subprocess.run(["git", "push", "origin", branch_name], check=True)
logging.info("Creating PR")
pr = repo.create_pull(title=message, body=message, base="master", head=branch_name)
logging.info(f"Created PR: {pr.number}")
logging.info("Finished")
if __name__ == "__main__":
main()
View File
Whitespace-only changes.
@@ -0,0 +1,86 @@
import inspect
import sys
from collections.abc import Callable
from typing import Any
import pytest
from fastapi import Depends, FastAPI
from fastapi.routing import APIRoute
if "--codspeed" not in sys.argv:
pytest.skip(
"Benchmark tests are skipped by default; run with --codspeed.",
allow_module_level=True,
)
LAST_DEPENDENCY_INDEX = 100
ENDPOINT_PARAMETER_COUNT = 50
ROUTE_PATH = "/dynamic-health"
def _create_app() -> FastAPI:
app = FastAPI()
dependencies: dict[int, Callable[..., Any]] = {}
def create_dependency(index: int) -> Callable[..., Any]:
if index == LAST_DEPENDENCY_INDEX:
def dependency() -> str:
return str(index)
dependency.__name__ = f"dependency_{index}"
return dependency
next_dependency = dependencies[index + 1]
async def dependency(
sub_dependency: str = Depends(next_dependency),
) -> str:
return f"{index} -> {sub_dependency}"
dependency.__name__ = f"dependency_{index}"
return dependency
for index in reversed(range(LAST_DEPENDENCY_INDEX + 1)):
dependencies[index] = create_dependency(index)
def create_endpoint() -> Callable[..., Any]:
parameters = [
inspect.Parameter(
name=f"arg_{index}",
kind=inspect.Parameter.POSITIONAL_OR_KEYWORD,
default=Depends(dependencies[index]),
annotation=str,
)
for index in range(ENDPOINT_PARAMETER_COUNT)
]
async def endpoint(**kwargs: str) -> dict[str, int]:
return {"parameter_count": len(kwargs)}
endpoint_with_signature: Any = endpoint
endpoint_with_signature.__signature__ = inspect.Signature(parameters)
return endpoint
for method in ("GET", "POST"):
app.add_api_route(
ROUTE_PATH,
create_endpoint(),
methods=[method],
)
return app
def test_dependency_graph(benchmark) -> None:
app = benchmark(_create_app)
dynamic_routes = [
route
for route in app.routes
if isinstance(route, APIRoute) and route.path == ROUTE_PATH
]
assert len(dynamic_routes) == 2
assert {method for route in dynamic_routes for method in route.methods} == {
"GET",
"POST",
}
@@ -0,0 +1,64 @@
import sys
from collections.abc import Callable
from typing import Annotated, Any
import pytest
from fastapi import Depends, FastAPI
from fastapi.routing import APIRoute
if "--codspeed" not in sys.argv:
pytest.skip(
"Benchmark tests are skipped by default; run with --codspeed.",
allow_module_level=True,
)
LAST_DEPENDENCY_INDEX = 100
ROUTE_COUNT = 20
ROUTE_PATH_PREFIX = "/route-"
def _create_app() -> FastAPI:
app = FastAPI()
dependencies: dict[int, Callable[..., Any]] = {}
def create_dependency(index: int) -> Callable[..., Any]:
if index == LAST_DEPENDENCY_INDEX:
def dependency() -> str:
return str(index)
dependency.__name__ = f"dependency_{index}"
return dependency
next_dependency = dependencies[index + 1]
async def dependency(
sub_dependency: Annotated[str, Depends(next_dependency)],
) -> str:
return f"{index} -> {sub_dependency}"
dependency.__name__ = f"dependency_{index}"
return dependency
for index in reversed(range(LAST_DEPENDENCY_INDEX + 1)):
dependencies[index] = create_dependency(index)
async def endpoint(
value: Annotated[str, Depends(dependencies[0])],
) -> dict[str, str]:
return {"value": value}
for index in range(ROUTE_COUNT):
app.add_api_route(f"{ROUTE_PATH_PREFIX}{index}", endpoint)
return app
def test_route_dependency_graph(benchmark) -> None:
app = benchmark(_create_app)
api_routes = [
route
for route in app.routes
if isinstance(route, APIRoute) and route.path.startswith(ROUTE_PATH_PREFIX)
]
assert len(api_routes) == ROUTE_COUNT
+207
View File
@@ -0,0 +1,207 @@
from collections.abc import AsyncGenerator, Callable, Generator
from typing import Any
from fastapi.dependencies.models import (
Dependant,
_get_cache_key,
_get_computed_scope,
_get_oauth_scopes,
_get_security_dependencies,
_get_security_scheme,
_is_async_gen_callable,
_is_async_gen_callable_cached,
_is_coroutine_callable,
_is_coroutine_callable_cached,
_is_gen_callable,
_is_gen_callable_cached,
_is_security_scheme,
_uses_scopes,
)
from fastapi.security import APIKeyHeader
def sync_dependency() -> None:
pass # pragma: no cover
async def async_dependency() -> None:
pass # pragma: no cover
def generator_dependency() -> Generator[None, None, None]:
yield # pragma: no cover
async def async_generator_dependency() -> AsyncGenerator[None, None]:
yield # pragma: no cover
class UnhashableCallable:
__hash__ = None
async def __call__(self) -> None:
pass # pragma: no cover
class UnhashableGeneratorCallable:
__hash__ = None
def __call__(self) -> Generator[None, None, None]:
yield # pragma: no cover
class UnhashableAsyncGeneratorCallable:
__hash__ = None
async def __call__(self) -> AsyncGenerator[None, None]:
yield # pragma: no cover
class EqualCallable:
def __eq__(self, other: object) -> bool:
return isinstance(other, EqualCallable)
def __hash__(self) -> int:
return 1 # pragma: no cover
class EqualAsyncCallable(EqualCallable):
async def __call__(self) -> None:
pass # pragma: no cover
class EqualSyncCallable(EqualCallable):
def __call__(self) -> None:
pass # pragma: no cover
def test_callable_classification_is_shared_by_call() -> None:
_is_gen_callable_cached.cache_clear()
_is_async_gen_callable_cached.cache_clear()
_is_coroutine_callable_cached.cache_clear()
for _ in range(2):
assert not _is_gen_callable(async_dependency)
assert not _is_async_gen_callable(async_dependency)
assert _is_coroutine_callable(async_dependency)
for cached_function in (
_is_gen_callable_cached,
_is_async_gen_callable_cached,
_is_coroutine_callable_cached,
):
cache_info = cached_function.cache_info()
assert cache_info.hits == 1
assert cache_info.misses == 1
assert cache_info.maxsize == 4096
def test_callable_classification_cache_supports_large_apps() -> None:
callables: list[Callable[[], None]] = [lambda: None for _ in range(3000)]
for classifier, cached_classifier in (
(_is_gen_callable, _is_gen_callable_cached),
(_is_async_gen_callable, _is_async_gen_callable_cached),
(_is_coroutine_callable, _is_coroutine_callable_cached),
):
cached_classifier.cache_clear()
for _ in range(2):
assert all(not classifier(call) for call in callables)
cache_info = cached_classifier.cache_info()
assert cache_info.hits == len(callables)
assert cache_info.misses == len(callables)
assert cache_info.maxsize == 4096
cached_classifier.cache_clear()
def test_unhashable_callable_classification() -> None:
assert _is_coroutine_callable(UnhashableCallable())
assert _is_gen_callable(UnhashableGeneratorCallable())
assert _is_async_gen_callable(UnhashableAsyncGeneratorCallable())
def test_equal_callable_instances_are_cached_by_identity() -> None:
async_callable = EqualAsyncCallable()
sync_callable = EqualSyncCallable()
assert async_callable == sync_callable
assert _is_coroutine_callable(async_callable)
assert not _is_coroutine_callable(sync_callable)
def test_callable_classification() -> None:
assert not _is_gen_callable(sync_dependency)
assert not _is_async_gen_callable(sync_dependency)
assert not _is_coroutine_callable(sync_dependency)
assert _is_gen_callable(generator_dependency)
assert _is_async_gen_callable(async_generator_dependency)
def test_derived_values_are_not_stored_on_dependant() -> None:
dependant = Dependant(call=async_dependency)
uses_scopes_cache = {}
assert _get_oauth_scopes(dependant=dependant) == []
assert not _uses_scopes(dependant=dependant, cache=uses_scopes_cache)
assert not _uses_scopes(dependant=dependant, cache=uses_scopes_cache)
assert _get_security_dependencies(dependant=dependant) == []
assert _get_computed_scope(dependant=dependant) is None
assert _get_cache_key(dependant=dependant) == (async_dependency, (), "")
assert not hasattr(dependant, "__dict__")
def test_security_scheme_helpers() -> None:
security_scheme = APIKeyHeader(name="key")
security_dependant = Dependant(call=security_scheme)
dependant = Dependant(dependencies=[security_dependant])
assert _is_security_scheme(dependant=security_dependant)
assert _get_security_scheme(dependant=security_dependant) is security_scheme
assert _get_security_dependencies(dependant=dependant) == [security_dependant]
assert _uses_scopes(dependant=dependant)
def test_derived_values_follow_dependency_state() -> None:
child = Dependant(call=sync_dependency)
dependant = Dependant(
call=sync_dependency,
dependencies=[child],
own_oauth_scopes=[],
parent_oauth_scopes=["parent"],
)
assert _get_cache_key(dependant=dependant) == (sync_dependency, (), "")
child.security_scopes_param_name = "scopes"
dependant.own_oauth_scopes = ["own", "parent"]
assert _uses_scopes(dependant=dependant)
assert _get_oauth_scopes(dependant=dependant) == ["parent", "own"]
assert _get_cache_key(dependant=dependant) == (
sync_dependency,
("own", "parent"),
"",
)
def test_explicit_and_generator_scopes() -> None:
assert (
_get_computed_scope(dependant=Dependant(call=sync_dependency, scope="function"))
== "function"
)
assert (
_get_computed_scope(dependant=Dependant(call=generator_dependency)) == "request"
)
def test_callable_return_annotations_are_not_used() -> None:
class CallableWithUnhashableReturn:
def __call__(self) -> Any:
return None # pragma: no cover
__hash__ = None
assert not _is_coroutine_callable(CallableWithUnhashableReturn())
Generated
+2 -2
View File
@@ -1064,7 +1064,7 @@ dev = [
{ name = "pygithub", specifier = ">=2.8.1" },
{ name = "pyjwt", specifier = ">=2.9.0" },
{ name = "pytest", specifier = ">=9.0.0" },
{ name = "pytest-codspeed", specifier = ">=4.2.0" },
{ name = "pytest-codspeed", specifier = ">=4.3.0" },
{ name = "pytest-cov", specifier = ">=4.0.0" },
{ name = "pytest-sugar", specifier = ">=1.0.0" },
{ name = "pytest-timeout", specifier = ">=2.4.0" },
@@ -1124,7 +1124,7 @@ tests = [
{ name = "pwdlib", extras = ["argon2"], specifier = ">=0.2.1" },
{ name = "pyjwt", specifier = ">=2.9.0" },
{ name = "pytest", specifier = ">=9.0.0" },
{ name = "pytest-codspeed", specifier = ">=4.2.0" },
{ name = "pytest-codspeed", specifier = ">=4.3.0" },
{ name = "pytest-cov", specifier = ">=4.0.0" },
{ name = "pytest-sugar", specifier = ">=1.0.0" },
{ name = "pytest-timeout", specifier = ">=2.4.0" },