mirror of
https://github.com/fastapi/fastapi.git
synced 2026-09-09 12:00:05 -04:00
Compare commits
No files matched your search
@@ -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]
|
||||
@@ -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 }}
|
||||
@@ -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:
|
||||
|
||||
@@ -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)):
|
||||
|
||||

|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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`.
|
||||
@@ -45,7 +45,7 @@ You can run your tests as usual via:
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ pytest
|
||||
$ uv run pytest
|
||||
|
||||
---> 100%
|
||||
```
|
||||
|
||||
@@ -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)
|
||||
```
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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).
|
||||
@@ -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`:
|
||||
|
||||
|
||||
@@ -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).
|
||||
@@ -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).
|
||||
@@ -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/).
|
||||
|
||||
///
|
||||
|
||||
|
||||
@@ -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)
|
||||
```
|
||||
|
||||
@@ -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/).
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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).
|
||||
@@ -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`.
|
||||
|
||||
///
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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)):
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -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...
|
||||
|
||||
|
||||
@@ -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)
|
||||
```
|
||||
|
||||
@@ -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>]
|
||||
|
||||
@@ -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.
|
||||
@@ -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
@@ -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. 🙌
|
||||
@@ -19,7 +19,7 @@ Interactive API documentation and exploration web user interfaces. As the framew
|
||||
|
||||

|
||||
|
||||
* Alternative API documentation with [**ReDoc**](https://github.com/Rebilly/ReDoc).
|
||||
* Alternative API documentation with [**ReDoc**](https://github.com/Redocly/redoc).
|
||||
|
||||

|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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/).
|
||||
|
||||
///
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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
@@ -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)):
|
||||
|
||||

|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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). ☕️
|
||||
@@ -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.
|
||||
|
||||
@@ -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/).
|
||||
|
||||
///
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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)
|
||||
```
|
||||
|
||||
@@ -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`:
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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]`.
|
||||
|
||||
///
|
||||
|
||||
|
||||
@@ -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)):
|
||||
|
||||

|
||||
|
||||
@@ -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.
|
||||
|
||||
///
|
||||
|
||||
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
///
|
||||
|
||||
|
||||
@@ -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. 🤓
|
||||
|
||||
///
|
||||
|
||||
|
||||
@@ -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".
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
///
|
||||
|
||||
@@ -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)
|
||||
```
|
||||
|
||||
@@ -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%
|
||||
```
|
||||
|
||||
@@ -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)
|
||||
```
|
||||
|
||||
@@ -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/).
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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?
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
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
|
||||
@@ -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())
|
||||
@@ -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" },
|
||||
|
||||
Reference in new issue
Block a user