mirror of
https://github.com/fastapi/fastapi.git
synced 2026-09-08 19:41:04 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
702fea83bb | ||
|
|
6466865eec | ||
|
|
b790e14cb6 | ||
|
|
c2708d9817 | ||
|
|
403b1fa16f | ||
|
|
1929ac2319 | ||
|
|
cba4158567 | ||
|
|
b90c49aefa | ||
|
|
1f2f3df485 | ||
|
|
0af003a85d | ||
|
|
d5494c1f01 | ||
|
|
82c527e714 | ||
|
|
3f28dbe6d8 | ||
|
|
f6ea6b1f12 | ||
|
|
219ac634ab | ||
|
|
fcc8e1f994 | ||
|
|
457f4c705c | ||
|
|
29fc8f6166 | ||
|
|
a497a025e7 | ||
|
|
8f3db3d3e7 | ||
|
|
0cb4a8e284 | ||
|
|
d69774c23d |
No files matched your search
@@ -4,10 +4,6 @@ on:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -29,29 +29,6 @@ jobs:
|
||||
env:
|
||||
GITHUB_CONTEXT: ${{ toJson(github) }}
|
||||
run: echo "$GITHUB_CONTEXT"
|
||||
- uses: tiangolo/issue-manager@2fb3484ec9279485df8659e8ec73de262431737d # 0.6.0
|
||||
- uses: tiangolo/issue-manager@75d60679db1ea348f6f6ea1d0e20de80a7c04645 # 0.7.1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
config: >
|
||||
{
|
||||
"answered": {
|
||||
"delay": 864000,
|
||||
"message": "Assuming the original need was handled, this will be automatically closed now. But feel free to add more comments or create new issues or PRs."
|
||||
},
|
||||
"waiting": {
|
||||
"delay": 2628000,
|
||||
"message": "As this PR has been waiting for the original user for a while but seems to be inactive, it's now going to be closed. But if there's anyone interested, feel free to create a new PR.",
|
||||
"reminder": {
|
||||
"before": "P3D",
|
||||
"message": "Heads-up: this will be closed in 3 days unless there's new activity."
|
||||
}
|
||||
},
|
||||
"invalid": {
|
||||
"delay": 0,
|
||||
"message": "This was marked as invalid and will be closed now. If this is an error, please provide additional details."
|
||||
},
|
||||
"maybe-ai": {
|
||||
"delay": 0,
|
||||
"message": "This was marked as potentially AI generated and will be closed now. If this is an error, please provide additional details, make sure to read the docs about contributing and AI."
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,6 @@ name: pre-commit
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
|
||||
@@ -5,10 +5,6 @@ on:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -5,9 +5,6 @@ on:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
schedule:
|
||||
# cron every week on monday
|
||||
- cron: "0 0 * * 1"
|
||||
|
||||
@@ -4,6 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
@@ -65,6 +65,13 @@ repos:
|
||||
files: ^docs/en/docs/index\.md|docs/en/data/sponsors\.yml|scripts/docs\.py$
|
||||
pass_filenames: false
|
||||
|
||||
- id: render-banner-sponsors
|
||||
language: unsupported
|
||||
name: render sponsor banner partial
|
||||
entry: uv run ./scripts/docs.py render-banner-sponsors
|
||||
files: ^docs/en/data/sponsors\.yml|^docs/en/overrides/partials/banner-sponsors\.html|^scripts/docs\.py$
|
||||
pass_filenames: false
|
||||
|
||||
- id: update-languages
|
||||
language: unsupported
|
||||
name: update languages
|
||||
|
||||
@@ -64,7 +64,6 @@ The key features are:
|
||||
|
||||
<a href="https://databento.com/?utm_source=fastapi&utm_medium=sponsor&utm_content=display" target="_blank" title="Pay as you go for market data"><img src="https://fastapi.tiangolo.com/img/sponsors/databento.svg"></a>
|
||||
<a href="https://www.svix.com/" target="_blank" title="Svix - Webhooks as a service"><img src="https://fastapi.tiangolo.com/img/sponsors/svix.svg"></a>
|
||||
<a href="https://www.stainlessapi.com/?utm_source=fastapi&utm_medium=referral" target="_blank" title="Stainless | Generate best-in-class SDKs"><img src="https://fastapi.tiangolo.com/img/sponsors/stainless.png"></a>
|
||||
<a href="https://www.permit.io/blog/implement-authorization-in-fastapi?utm_source=github&utm_medium=referral&utm_campaign=fastapi" target="_blank" title="Fine-Grained Authorization for FastAPI"><img src="https://fastapi.tiangolo.com/img/sponsors/permit.png"></a>
|
||||
<a href="https://dribia.com/en/" target="_blank" title="Dribia - Data Science within your reach"><img src="https://fastapi.tiangolo.com/img/sponsors/dribia.png"></a>
|
||||
<a href="https://www.rapidproxy.io/?ref=fastapi" target="_blank" title="Try RapidProxy for free - Residential Proxies with 90M+ Global IPs. Starting from $0.65/GB for web scraping, automation, and data collection."><img src="https://fastapi.tiangolo.com/img/sponsors/rapidproxy.png"></a>
|
||||
|
||||
@@ -6,27 +6,38 @@ gold:
|
||||
- url: https://blockbee.io?ref=fastapi
|
||||
title: BlockBee Cryptocurrency Payment Gateway
|
||||
img: /img/sponsors/blockbee.png
|
||||
banner_img: /img/sponsors/blockbee-banner.png
|
||||
- url: https://www.propelauth.com/?utm_source=fastapi&utm_campaign=1223&utm_medium=mainbadge
|
||||
title: Auth, user management and more for your B2B product
|
||||
img: /img/sponsors/propelauth.png
|
||||
banner_url: https://www.propelauth.com/?utm_source=fastapi&utm_campaign=1223&utm_medium=topbanner
|
||||
banner_img: /img/sponsors/propelauth-banner.png
|
||||
- url: https://docs.render.com/deploy-fastapi?utm_source=deploydoc&utm_medium=referral&utm_campaign=fastapi
|
||||
title: Deploy & scale any full-stack web app on Render. Focus on building apps, not infra.
|
||||
img: /img/sponsors/render.svg
|
||||
banner_img: /img/sponsors/render-banner.svg
|
||||
- url: https://www.coderabbit.ai/?utm_source=fastapi&utm_medium=badge&utm_campaign=fastapi
|
||||
title: Cut Code Review Time & Bugs in Half with CodeRabbit
|
||||
img: /img/sponsors/coderabbit.png
|
||||
banner_url: https://www.coderabbit.ai/?utm_source=fastapi&utm_medium=banner&utm_campaign=fastapi
|
||||
banner_img: /img/sponsors/coderabbit-banner.png
|
||||
- url: https://subtotal.com/?utm_source=fastapi&utm_medium=sponsorship&utm_campaign=open-source
|
||||
title: The Gold Standard in Retail Account Linking
|
||||
img: /img/sponsors/subtotal.svg
|
||||
banner_title: Making Retail Purchases Actionable for Brands and Developers
|
||||
banner_img: /img/sponsors/subtotal-banner.svg
|
||||
- url: https://docs.railway.com/guides/fastapi?utm_medium=integration&utm_source=docs&utm_campaign=fastapi
|
||||
title: Deploy enterprise applications at startup speed
|
||||
img: /img/sponsors/railway.png
|
||||
banner_img: /img/sponsors/railway-banner.png
|
||||
- url: https://serpapi.com/?utm_source=fastapi_website
|
||||
title: "SerpApi: Web Search API"
|
||||
img: /img/sponsors/serpapi.png
|
||||
banner_img: /img/sponsors/serpapi-banner.png
|
||||
- url: https://www.greptile.com/?utm_source=fastapi&utm_medium=sponsorship&utm_campaign=fastapi_sponsor_page
|
||||
title: "Greptile: The AI Code Reviewer"
|
||||
img: /img/sponsors/greptile.png
|
||||
banner_img: /img/sponsors/greptile-banner.png
|
||||
silver:
|
||||
- url: https://databento.com/?utm_source=fastapi&utm_medium=sponsor&utm_content=display
|
||||
title: Pay as you go for market data
|
||||
@@ -34,9 +45,6 @@ silver:
|
||||
- url: https://www.svix.com/
|
||||
title: Svix - Webhooks as a service
|
||||
img: /img/sponsors/svix.svg
|
||||
- url: https://www.stainlessapi.com/?utm_source=fastapi&utm_medium=referral
|
||||
title: Stainless | Generate best-in-class SDKs
|
||||
img: /img/sponsors/stainless.png
|
||||
- url: https://www.permit.io/blog/implement-authorization-in-fastapi?utm_source=github&utm_medium=referral&utm_campaign=fastapi
|
||||
title: Fine-Grained Authorization for FastAPI
|
||||
img: /img/sponsors/permit.png
|
||||
@@ -53,3 +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
|
||||
title: TestMu AI. The Native AI-Agentic Cloud Platform to Supercharge Quality Engineering.
|
||||
img: /img/sponsors/testmu.png
|
||||
@@ -20,20 +20,6 @@ FastAPI automatically generates **OpenAPI 3.1** specifications, so any tool you
|
||||
|
||||
///
|
||||
|
||||
## SDK Generators from FastAPI Sponsors { #sdk-generators-from-fastapi-sponsors }
|
||||
|
||||
This section highlights **venture-backed** and **company-supported** solutions from companies that sponsor FastAPI. These products provide **additional features** and **integrations** on top of high-quality generated SDKs.
|
||||
|
||||
By ✨ [**sponsoring FastAPI**](https://github.com/sponsors/tiangolo) ✨, these companies help ensure the framework and its **ecosystem** remain healthy and **sustainable**.
|
||||
|
||||
Their sponsorship also demonstrates a strong commitment to the FastAPI **community** (you), showing that they care not only about offering a **great service** but also about supporting a **robust and thriving framework**, FastAPI. 🙇
|
||||
|
||||
For example, you might want to try:
|
||||
|
||||
* [Stainless](https://www.stainless.com/?utm_source=fastapi&utm_medium=referral)
|
||||
|
||||
Some of these solutions may also be open source or offer free tiers, so you can try them without a financial commitment. Other commercial SDK generators are available and can be found online. 🤓
|
||||
|
||||
## Create a TypeScript SDK { #create-a-typescript-sdk }
|
||||
|
||||
Let's start with a simple FastAPI application:
|
||||
|
||||
@@ -249,6 +249,16 @@ They are supporting my work with **FastAPI** (and others), mainly through [GitHu
|
||||
<a href="{{ sponsor.url }}" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if sponsors.bronze %}
|
||||
|
||||
### Bronze Sponsors
|
||||
|
||||
{% for sponsor in sponsors.bronze -%}
|
||||
<a href="{{ sponsor.url }}" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
### Individual Sponsors
|
||||
|
||||
@@ -7,6 +7,32 @@ hide:
|
||||
|
||||
## Latest Changes
|
||||
|
||||
## 0.138.2 (2026-06-29)
|
||||
|
||||
### Refactors
|
||||
|
||||
* ♻️ Make `app.frontend()` return 404 for methods other than `GET` or `HEAD` with no static file matches. PR [#15863](https://github.com/fastapi/fastapi/pull/15863) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
### Internal
|
||||
|
||||
* 🔧 Update sponsors: remove Stainless. PR [#15862](https://github.com/fastapi/fastapi/pull/15862) by [@tiangolo](https://github.com/tiangolo).
|
||||
* ♻️ Refactor how sponsors data is handled for banners. PR [#15852](https://github.com/fastapi/fastapi/pull/15852) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
## 0.138.1 (2026-06-25)
|
||||
|
||||
### Refactors
|
||||
|
||||
* ♻️ Refactor Library Skills, make info easier to find for agents. PR [#15841](https://github.com/fastapi/fastapi/pull/15841) by [@tiangolo](https://github.com/tiangolo).
|
||||
|
||||
### Internal
|
||||
|
||||
* 👷 Simplify pull request workflow triggers. PR [#15836](https://github.com/fastapi/fastapi/pull/15836) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 👷 Update issue-manager to 0.7.1. PR [#15833](https://github.com/fastapi/fastapi/pull/15833) by [@tiangolo](https://github.com/tiangolo).
|
||||
* ⬆️ Update issue-manager to 0.7.0. PR [#15831](https://github.com/fastapi/fastapi/pull/15831) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 🔧 Update sponsors: Add TestMu again. PR [#15830](https://github.com/fastapi/fastapi/pull/15830) by [@tiangolo](https://github.com/tiangolo).
|
||||
* 🔒️ Update zizmor workflow security checks. PR [#15820](https://github.com/fastapi/fastapi/pull/15820) by [@tiangolo](https://github.com/tiangolo).
|
||||
* ⬆ Bump pydantic-settings from 2.14.1 to 2.14.2. PR [#15799](https://github.com/fastapi/fastapi/pull/15799) by [@dependabot[bot]](https://github.com/apps/dependabot).
|
||||
|
||||
## 0.138.0 (2026-06-20)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -52,7 +52,9 @@ For that, use `fallback="index.html"`:
|
||||
|
||||
{* ../../docs_src/frontend/tutorial002_py310.py hl[5] *}
|
||||
|
||||
**FastAPI** uses this fallback only for requests that look like browser navigation. Missing files like JavaScript, CSS, and images still return `404`.
|
||||
**FastAPI** uses this fallback only for `GET` and `HEAD` requests that look like browser navigation. Missing files like JavaScript, CSS, and images still return `404`.
|
||||
|
||||
Requests with other methods, like `POST` or `PUT`, to paths that only match the frontend fallback also return `404`. Regular **FastAPI** *path operations* still have higher priority than frontend routes.
|
||||
|
||||
/// tip
|
||||
|
||||
|
||||
@@ -40,54 +40,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="announce-right" style="position: relative;">
|
||||
<div class="item">
|
||||
<a title="BlockBee Cryptocurrency Payment Gateway" style="display: block; position: relative;" href="https://blockbee.io?ref=fastapi" target="_blank">
|
||||
<span class="sponsor-badge">sponsor</span>
|
||||
<img class="sponsor-image" src="/img/sponsors/blockbee-banner.png" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a title="Auth, user management and more for your B2B product" style="display: block; position: relative;" href="https://www.propelauth.com/?utm_source=fastapi&utm_campaign=1223&utm_medium=topbanner" target="_blank">
|
||||
<span class="sponsor-badge">sponsor</span>
|
||||
<img class="sponsor-image" src="/img/sponsors/propelauth-banner.png" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a title="Deploy & scale any full-stack web app on Render. Focus on building apps, not infra." style="display: block; position: relative;" href="https://docs.render.com/deploy-fastapi?utm_source=deploydoc&utm_medium=referral&utm_campaign=fastapi" target="_blank">
|
||||
<span class="sponsor-badge">sponsor</span>
|
||||
<img class="sponsor-image" src="/img/sponsors/render-banner.svg" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a title="Cut Code Review Time & Bugs in Half with CodeRabbit" style="display: block; position: relative;" href="https://www.coderabbit.ai/?utm_source=fastapi&utm_medium=banner&utm_campaign=fastapi" target="_blank">
|
||||
<span class="sponsor-badge">sponsor</span>
|
||||
<img class="sponsor-image" src="/img/sponsors/coderabbit-banner.png" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a title="Making Retail Purchases Actionable for Brands and Developers" style="display: block; position: relative;" href="https://subtotal.com/?utm_source=fastapi&utm_medium=sponsorship&utm_campaign=open-source" target="_blank">
|
||||
<span class="sponsor-badge">sponsor</span>
|
||||
<img class="sponsor-image" src="/img/sponsors/subtotal-banner.svg" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a title="Deploy enterprise applications at startup speed" style="display: block; position: relative;" href="https://docs.railway.com/guides/fastapi?utm_medium=integration&utm_source=docs&utm_campaign=fastapi" target="_blank">
|
||||
<span class="sponsor-badge">sponsor</span>
|
||||
<img class="sponsor-image" src="/img/sponsors/railway-banner.png" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a title="SerpApi: Web Search API" style="display: block; position: relative;" href="https://serpapi.com/?utm_source=fastapi_website" target="_blank">
|
||||
<span class="sponsor-badge">sponsor</span>
|
||||
<img class="sponsor-image" src="/img/sponsors/serpapi-banner.png" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a title="Greptile: The AI Code Reviewer" style="display: block; position: relative;" href="https://www.greptile.com/?utm_source=fastapi&utm_medium=sponsorship&utm_campaign=fastapi_sponsor_page" target="_blank">
|
||||
<span class="sponsor-badge">sponsor</span>
|
||||
<img class="sponsor-image" src="/img/sponsors/greptile-banner.png" />
|
||||
</a>
|
||||
</div>
|
||||
{% include "partials/banner-sponsors.html" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,48 @@
|
||||
<div class="item">
|
||||
<a title="BlockBee Cryptocurrency Payment Gateway" style="display: block; position: relative;" href="https://blockbee.io?ref=fastapi" target="_blank">
|
||||
<span class="sponsor-badge">sponsor</span>
|
||||
<img class="sponsor-image" src="/img/sponsors/blockbee-banner.png" alt="BlockBee Cryptocurrency Payment Gateway" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a title="Auth, user management and more for your B2B product" style="display: block; position: relative;" href="https://www.propelauth.com/?utm_source=fastapi&utm_campaign=1223&utm_medium=topbanner" target="_blank">
|
||||
<span class="sponsor-badge">sponsor</span>
|
||||
<img class="sponsor-image" src="/img/sponsors/propelauth-banner.png" alt="Auth, user management and more for your B2B product" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a title="Deploy & scale any full-stack web app on Render. Focus on building apps, not infra." style="display: block; position: relative;" href="https://docs.render.com/deploy-fastapi?utm_source=deploydoc&utm_medium=referral&utm_campaign=fastapi" target="_blank">
|
||||
<span class="sponsor-badge">sponsor</span>
|
||||
<img class="sponsor-image" src="/img/sponsors/render-banner.svg" alt="Deploy & scale any full-stack web app on Render. Focus on building apps, not infra." />
|
||||
</a>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a title="Cut Code Review Time & Bugs in Half with CodeRabbit" style="display: block; position: relative;" href="https://www.coderabbit.ai/?utm_source=fastapi&utm_medium=banner&utm_campaign=fastapi" target="_blank">
|
||||
<span class="sponsor-badge">sponsor</span>
|
||||
<img class="sponsor-image" src="/img/sponsors/coderabbit-banner.png" alt="Cut Code Review Time & Bugs in Half with CodeRabbit" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a title="Making Retail Purchases Actionable for Brands and Developers" style="display: block; position: relative;" href="https://subtotal.com/?utm_source=fastapi&utm_medium=sponsorship&utm_campaign=open-source" target="_blank">
|
||||
<span class="sponsor-badge">sponsor</span>
|
||||
<img class="sponsor-image" src="/img/sponsors/subtotal-banner.svg" alt="Making Retail Purchases Actionable for Brands and Developers" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a title="Deploy enterprise applications at startup speed" style="display: block; position: relative;" href="https://docs.railway.com/guides/fastapi?utm_medium=integration&utm_source=docs&utm_campaign=fastapi" target="_blank">
|
||||
<span class="sponsor-badge">sponsor</span>
|
||||
<img class="sponsor-image" src="/img/sponsors/railway-banner.png" alt="Deploy enterprise applications at startup speed" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a title="SerpApi: Web Search API" style="display: block; position: relative;" href="https://serpapi.com/?utm_source=fastapi_website" target="_blank">
|
||||
<span class="sponsor-badge">sponsor</span>
|
||||
<img class="sponsor-image" src="/img/sponsors/serpapi-banner.png" alt="SerpApi: Web Search API" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="item">
|
||||
<a title="Greptile: The AI Code Reviewer" style="display: block; position: relative;" href="https://www.greptile.com/?utm_source=fastapi&utm_medium=sponsorship&utm_campaign=fastapi_sponsor_page" target="_blank">
|
||||
<span class="sponsor-badge">sponsor</span>
|
||||
<img class="sponsor-image" src="/img/sponsors/greptile-banner.png" alt="Greptile: The AI Code Reviewer" />
|
||||
</a>
|
||||
</div>
|
||||
@@ -1,12 +1,23 @@
|
||||
---
|
||||
name: fastapi
|
||||
description: FastAPI best practices and conventions. Use when working with FastAPI APIs and Pydantic models for them. Keeps FastAPI code clean and up to date with the latest features and patterns, updated with new versions. Write new code or refactor and update old code.
|
||||
description: FastAPI best practices and conventions. Use when working with FastAPI APIs, Pydantic models, dependencies, streaming responses including Server-Sent Events (SSE), and serving frontend apps. Keeps FastAPI code clean and up to date with the latest features and patterns.
|
||||
---
|
||||
|
||||
# FastAPI
|
||||
|
||||
Official FastAPI skill to write code with best practices, keeping up to date with new versions and features.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
* Serve frontend apps: use `app.frontend()` or `router.frontend()` for built frontend assets; see [Serve Frontend Apps](#serve-frontend-apps).
|
||||
* Server-Sent Events (SSE): use `response_class=EventSourceResponse` and `yield`; see [Streaming](#streaming-json-lines-sse-bytes) and [the streaming reference](references/streaming.md).
|
||||
* JSON Lines and byte streaming: see [the streaming reference](references/streaming.md).
|
||||
* Dependencies: use `Annotated[..., Depends(...)]`; see [Dependency Injection](#dependency-injection) and [the dependency injection reference](references/dependencies.md) for `yield`, scopes, and class dependencies.
|
||||
* Response models: prefer return types; use `response_model` when the public response schema differs from the internal return value; see [the response reference](references/responses.md).
|
||||
* Pydantic models: do not use ellipsis or `RootModel`; see [the Pydantic reference](references/pydantic.md).
|
||||
* Routing: declare router-level prefix, tags, and shared dependencies on the `APIRouter`; see [the path operation reference](references/path-operations.md).
|
||||
* Tooling and related libraries: use uv, Ruff, ty, Asyncer, SQLModel, and HTTPX when applicable; see [the other tools reference](references/other-tools.md).
|
||||
|
||||
## Use the `fastapi` CLI
|
||||
|
||||
Run the development server on localhost with reload:
|
||||
@@ -15,39 +26,28 @@ Run the development server on localhost with reload:
|
||||
fastapi dev
|
||||
```
|
||||
|
||||
|
||||
Run the production server:
|
||||
|
||||
```bash
|
||||
fastapi run
|
||||
```
|
||||
|
||||
### Add an entrypoint in `pyproject.toml`
|
||||
|
||||
FastAPI CLI will read the entrypoint in `pyproject.toml` to know where the FastAPI app is declared.
|
||||
Prefer declaring the entrypoint in `pyproject.toml`:
|
||||
|
||||
```toml
|
||||
[tool.fastapi]
|
||||
entrypoint = "my_app.main:app"
|
||||
```
|
||||
|
||||
### Use `fastapi` with a path
|
||||
|
||||
When adding the entrypoint to `pyproject.toml` is not possible, or the user explicitly asks not to, or it's running an independent small app, you can pass the app file path to the `fastapi` command:
|
||||
When adding the entrypoint is not possible, or the user explicitly asks not to, pass the app file path:
|
||||
|
||||
```bash
|
||||
fastapi dev my_app/main.py
|
||||
```
|
||||
|
||||
Prefer to set the entrypoint in `pyproject.toml` when possible.
|
||||
|
||||
## Use `Annotated`
|
||||
|
||||
Always prefer the `Annotated` style for parameter and dependency declarations.
|
||||
|
||||
It keeps the function signatures working in other contexts, respects the types, allows reusability.
|
||||
|
||||
### In Parameter Declarations
|
||||
Always prefer the `Annotated` style for parameter and dependency declarations. It keeps function signatures working in other contexts, respects the types, and allows reusability.
|
||||
|
||||
Use `Annotated` for parameter declarations, including `Path`, `Query`, `Header`, etc.:
|
||||
|
||||
@@ -67,23 +67,7 @@ async def read_item(
|
||||
return {"message": "Hello World"}
|
||||
```
|
||||
|
||||
instead of:
|
||||
|
||||
```python
|
||||
# DO NOT DO THIS
|
||||
@app.get("/items/{item_id}")
|
||||
async def read_item(
|
||||
item_id: int = Path(ge=1, description="The item ID"),
|
||||
q: str | None = Query(default=None, max_length=50),
|
||||
):
|
||||
return {"message": "Hello World"}
|
||||
```
|
||||
|
||||
### For Dependencies
|
||||
|
||||
Use `Annotated` for dependencies with `Depends()`.
|
||||
|
||||
Unless asked not to, create a new type alias for the dependency to allow re-using it.
|
||||
Use `Annotated` for dependencies with `Depends()`. Unless asked not to, create a new type alias for the dependency to allow reusing it:
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
@@ -105,20 +89,9 @@ async def read_item(current_user: CurrentUserDep):
|
||||
return {"message": "Hello World"}
|
||||
```
|
||||
|
||||
instead of:
|
||||
|
||||
```python
|
||||
# DO NOT DO THIS
|
||||
@app.get("/items/")
|
||||
async def read_item(current_user: dict = Depends(get_current_user)):
|
||||
return {"message": "Hello World"}
|
||||
```
|
||||
|
||||
## Do not use Ellipsis for *path operations* or Pydantic models
|
||||
|
||||
Do not use `...` as a default value for required parameters, it's not needed and not recommended.
|
||||
|
||||
Do this, without Ellipsis (`...`):
|
||||
Do not use `...` as a default value for required parameters or model fields. It's not needed and not recommended.
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
@@ -126,6 +99,8 @@ from typing import Annotated
|
||||
from fastapi import FastAPI, Query
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
@@ -133,29 +108,12 @@ class Item(BaseModel):
|
||||
price: float = Field(gt=0)
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.post("/items/")
|
||||
async def create_item(item: Item, project_id: Annotated[int, Query()]): ...
|
||||
async def create_item(item: Item, project_id: Annotated[int, Query()]):
|
||||
return item
|
||||
```
|
||||
|
||||
instead of this:
|
||||
|
||||
```python
|
||||
# DO NOT DO THIS
|
||||
class Item(BaseModel):
|
||||
name: str = ...
|
||||
description: str | None = None
|
||||
price: float = Field(..., gt=0)
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.post("/items/")
|
||||
async def create_item(item: Item, project_id: Annotated[int, Query(...)]): ...
|
||||
```
|
||||
See [the Pydantic reference](references/pydantic.md) for more details.
|
||||
|
||||
## Return Type or Response Model
|
||||
|
||||
@@ -178,62 +136,9 @@ async def get_item() -> Item:
|
||||
return Item(name="Plumbus", description="All-purpose home device")
|
||||
```
|
||||
|
||||
**Important**: Return types or response models are what filter data ensuring no sensitive information is exposed. And they are used to serialize data with Pydantic (in Rust), this is the main idea that can increase response performance.
|
||||
Return types or response models filter data to avoid exposing sensitive information, and they let Pydantic serialize the data on the Rust side for performance.
|
||||
|
||||
The return type doesn't have to be a Pydantic model, it could be a different type, like a list of integers, or a dict, etc.
|
||||
|
||||
### When to use `response_model` instead
|
||||
|
||||
If the return type is not the same as the type that you want to use to validate, filter, or serialize, use the `response_model` parameter on the decorator instead.
|
||||
|
||||
```python
|
||||
from typing import Any
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
description: str | None = None
|
||||
|
||||
|
||||
@app.get("/items/me", response_model=Item)
|
||||
async def get_item() -> Any:
|
||||
return {"name": "Foo", "description": "A very nice Item"}
|
||||
```
|
||||
|
||||
This can be particularly useful when filtering data to expose only the public fields and avoid exposing sensitive information.
|
||||
|
||||
```python
|
||||
from typing import Any
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class InternalItem(BaseModel):
|
||||
name: str
|
||||
description: str | None = None
|
||||
secret_key: str
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
description: str | None = None
|
||||
|
||||
|
||||
@app.get("/items/me", response_model=Item)
|
||||
async def get_item() -> Any:
|
||||
item = InternalItem(
|
||||
name="Foo", description="A very nice Item", secret_key="supersecret"
|
||||
)
|
||||
return item
|
||||
```
|
||||
Use `response_model` when the type you return is not the same as the public schema you want to validate, filter, document, and serialize. See [the response reference](references/responses.md).
|
||||
|
||||
## Performance
|
||||
|
||||
@@ -243,16 +148,23 @@ Instead, declare a return type or response model. Pydantic will handle the data
|
||||
|
||||
## Including Routers
|
||||
|
||||
When declaring routers, prefer to add router level parameters like prefix, tags, etc. to the router itself, instead of in `include_router()`.
|
||||
|
||||
Do this:
|
||||
When declaring routers, prefer to add router-level parameters like prefix, tags, and shared dependencies to the router itself instead of in `include_router()`.
|
||||
|
||||
```python
|
||||
from fastapi import APIRouter, FastAPI
|
||||
from fastapi import APIRouter, Depends, FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
router = APIRouter(prefix="/items", tags=["items"])
|
||||
|
||||
def get_current_user():
|
||||
return {"username": "johndoe"}
|
||||
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/items",
|
||||
tags=["items"],
|
||||
dependencies=[Depends(get_current_user)],
|
||||
)
|
||||
|
||||
|
||||
@router.get("/")
|
||||
@@ -260,33 +172,10 @@ async def list_items():
|
||||
return []
|
||||
|
||||
|
||||
# In main.py
|
||||
app.include_router(router)
|
||||
```
|
||||
|
||||
instead of this:
|
||||
|
||||
```python
|
||||
# DO NOT DO THIS
|
||||
from fastapi import APIRouter, FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/")
|
||||
async def list_items():
|
||||
return []
|
||||
|
||||
|
||||
# In main.py
|
||||
app.include_router(router, prefix="/items", tags=["items"])
|
||||
```
|
||||
|
||||
There could be exceptions, but try to follow this convention.
|
||||
|
||||
Apply shared dependencies at the router level via `dependencies=[Depends(...)]`.
|
||||
See [the path operation reference](references/path-operations.md) for more routing patterns.
|
||||
|
||||
## Serve Frontend Apps
|
||||
|
||||
@@ -316,15 +205,15 @@ app.include_router(router)
|
||||
|
||||
## Dependency Injection
|
||||
|
||||
See [the dependency injection reference](references/dependencies.md) for detailed patterns including `yield` with `scope`, and class dependencies.
|
||||
|
||||
Use dependencies when the logic can't be declared in Pydantic validation, depends on external resources, needs cleanup (with `yield`), or is shared across endpoints.
|
||||
Use dependencies when the logic can't be declared in Pydantic validation, depends on external resources, needs cleanup with `yield`, or is shared across endpoints.
|
||||
|
||||
Apply shared dependencies at the router level via `dependencies=[Depends(...)]`.
|
||||
|
||||
See [the dependency injection reference](references/dependencies.md) for detailed patterns including `yield` with `scope`, and class dependencies.
|
||||
|
||||
## Async vs Sync *path operations*
|
||||
|
||||
Use `async` *path operations* only when fully certain that the logic called inside is compatible with async and await (it's called with `await`) or that it doesn't block.
|
||||
Use `async` *path operations* only when fully certain that the logic called inside is compatible with async and await, and that it doesn't block.
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI
|
||||
@@ -332,30 +221,44 @@ from fastapi import FastAPI
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
# Use async def when calling async code
|
||||
@app.get("/async-items/")
|
||||
async def read_async_items():
|
||||
data = await some_async_library.fetch_items()
|
||||
return data
|
||||
|
||||
|
||||
# Use plain def when calling blocking/sync code or when in doubt
|
||||
@app.get("/items/")
|
||||
def read_items():
|
||||
data = some_blocking_library.fetch_items()
|
||||
return data
|
||||
```
|
||||
|
||||
In case of doubt, or by default, use regular `def` functions, those will be run in a threadpool so they don't block the event loop.
|
||||
In case of doubt, or by default, use regular `def` functions. They will be run in a threadpool so they don't block the event loop. The same rules apply to dependencies.
|
||||
|
||||
The same rules apply to dependencies.
|
||||
|
||||
Make sure blocking code is not run inside of `async` functions. The logic will work, but will damage the performance heavily.
|
||||
Make sure blocking code is not run inside of `async` functions. The logic will work, but will damage performance heavily.
|
||||
|
||||
When needing to mix blocking and async code, see Asyncer in [the other tools reference](references/other-tools.md).
|
||||
|
||||
## Streaming (JSON Lines, SSE, bytes)
|
||||
|
||||
To stream Server-Sent Events, use `response_class=EventSourceResponse` and `yield` items from the endpoint.
|
||||
|
||||
```python
|
||||
from collections.abc import AsyncIterable
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.sse import EventSourceResponse, ServerSentEvent
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/events", response_class=EventSourceResponse)
|
||||
async def stream_events() -> AsyncIterable[ServerSentEvent]:
|
||||
yield ServerSentEvent(data={"status": "started"}, event="status", id="1")
|
||||
```
|
||||
|
||||
Plain objects are automatically JSON-serialized as `data:` fields. Use `ServerSentEvent` for full control over SSE fields (`event`, `id`, `retry`, `comment`) and `raw_data` for pre-formatted strings.
|
||||
|
||||
See [the streaming reference](references/streaming.md) for JSON Lines, Server-Sent Events (`EventSourceResponse`, `ServerSentEvent`), and byte streaming (`StreamingResponse`) patterns.
|
||||
|
||||
## Tooling
|
||||
@@ -372,9 +275,7 @@ See [the other tools reference](references/other-tools.md) for details on other
|
||||
|
||||
## Do not use Pydantic RootModels
|
||||
|
||||
Do not use Pydantic `RootModel`, instead use regular type annotations with `Annotated` and Pydantic validation utilities.
|
||||
|
||||
For example, for a list with validations you could do:
|
||||
Do not use Pydantic `RootModel`; instead use regular type annotations with `Annotated` and Pydantic validation utilities.
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
@@ -390,35 +291,11 @@ async def create_items(items: Annotated[list[int], Field(min_length=1), Body()])
|
||||
return items
|
||||
```
|
||||
|
||||
instead of:
|
||||
|
||||
```python
|
||||
# DO NOT DO THIS
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import Field, RootModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class ItemList(RootModel[Annotated[list[int], Field(min_length=1)]]):
|
||||
pass
|
||||
|
||||
|
||||
@app.post("/items/")
|
||||
async def create_items(items: ItemList):
|
||||
return items
|
||||
|
||||
```
|
||||
|
||||
FastAPI supports these type annotations and will create a Pydantic `TypeAdapter` for them, so that types can work as normally and there's no need for the custom logic and types in RootModels.
|
||||
FastAPI supports these type annotations and will create a Pydantic `TypeAdapter` for them, so types work normally without custom wrapper models. See [the Pydantic reference](references/pydantic.md).
|
||||
|
||||
## Use one HTTP operation per function
|
||||
|
||||
Don't mix HTTP operations in a single function, having one function per HTTP operation helps separate concerns and organize the code.
|
||||
|
||||
Do this:
|
||||
Don't mix HTTP operations in a single function. Having one function per HTTP operation helps separate concerns and organize the code.
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI
|
||||
@@ -441,22 +318,4 @@ async def create_item(item: Item):
|
||||
return item
|
||||
```
|
||||
|
||||
instead of this:
|
||||
|
||||
```python
|
||||
# DO NOT DO THIS
|
||||
from fastapi import FastAPI, Request
|
||||
from pydantic import BaseModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
|
||||
|
||||
@app.api_route("/items/", methods=["GET", "POST"])
|
||||
async def handle_items(request: Request):
|
||||
if request.method == "GET":
|
||||
return []
|
||||
```
|
||||
See [the path operation reference](references/path-operations.md) for more examples.
|
||||
@@ -0,0 +1,93 @@
|
||||
# Path Operations and Routing
|
||||
|
||||
## Including Routers
|
||||
|
||||
When declaring routers, prefer to add router-level parameters like prefix, tags, and shared dependencies to the router itself instead of in `include_router()`.
|
||||
|
||||
Do this:
|
||||
|
||||
```python
|
||||
from fastapi import APIRouter, FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
router = APIRouter(prefix="/items", tags=["items"])
|
||||
|
||||
|
||||
@router.get("/")
|
||||
async def list_items():
|
||||
return []
|
||||
|
||||
|
||||
app.include_router(router)
|
||||
```
|
||||
|
||||
Instead of:
|
||||
|
||||
```python
|
||||
# DO NOT DO THIS
|
||||
from fastapi import APIRouter, FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/")
|
||||
async def list_items():
|
||||
return []
|
||||
|
||||
|
||||
app.include_router(router, prefix="/items", tags=["items"])
|
||||
```
|
||||
|
||||
There could be exceptions, but try to follow this convention.
|
||||
|
||||
Apply shared dependencies at the router level via `dependencies=[Depends(...)]`.
|
||||
|
||||
## Use one HTTP operation per function
|
||||
|
||||
Don't mix HTTP operations in a single function. Having one function per HTTP operation helps separate concerns and organize the code.
|
||||
|
||||
Do this:
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
|
||||
|
||||
@app.get("/items/")
|
||||
async def list_items():
|
||||
return []
|
||||
|
||||
|
||||
@app.post("/items/")
|
||||
async def create_item(item: Item):
|
||||
return item
|
||||
```
|
||||
|
||||
Instead of:
|
||||
|
||||
```python
|
||||
# DO NOT DO THIS
|
||||
from fastapi import FastAPI, Request
|
||||
from pydantic import BaseModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
|
||||
|
||||
@app.api_route("/items/", methods=["GET", "POST"])
|
||||
async def handle_items(request: Request):
|
||||
if request.method == "GET":
|
||||
return []
|
||||
```
|
||||
@@ -0,0 +1,93 @@
|
||||
# Pydantic
|
||||
|
||||
## Do not use Ellipsis
|
||||
|
||||
Do not use `...` as a default value for required parameters or model fields. It's not needed and not recommended.
|
||||
|
||||
Do this, without Ellipsis (`...`):
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import FastAPI, Query
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
description: str | None = None
|
||||
price: float = Field(gt=0)
|
||||
|
||||
|
||||
@app.post("/items/")
|
||||
async def create_item(item: Item, project_id: Annotated[int, Query()]):
|
||||
return item
|
||||
```
|
||||
|
||||
Instead of:
|
||||
|
||||
```python
|
||||
# DO NOT DO THIS
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import FastAPI, Query
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str = ...
|
||||
description: str | None = None
|
||||
price: float = Field(..., gt=0)
|
||||
|
||||
|
||||
@app.post("/items/")
|
||||
async def create_item(item: Item, project_id: Annotated[int, Query(...)]):
|
||||
return item
|
||||
```
|
||||
|
||||
## Do not use Pydantic RootModels
|
||||
|
||||
Do not use Pydantic `RootModel`; instead use regular type annotations with `Annotated` and Pydantic validation utilities.
|
||||
|
||||
For example, for a list with validations:
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Body, FastAPI
|
||||
from pydantic import Field
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.post("/items/")
|
||||
async def create_items(items: Annotated[list[int], Field(min_length=1), Body()]):
|
||||
return items
|
||||
```
|
||||
|
||||
Instead of:
|
||||
|
||||
```python
|
||||
# DO NOT DO THIS
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import Field, RootModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class ItemList(RootModel[Annotated[list[int], Field(min_length=1)]]):
|
||||
pass
|
||||
|
||||
|
||||
@app.post("/items/")
|
||||
async def create_items(items: ItemList):
|
||||
return items
|
||||
```
|
||||
|
||||
FastAPI supports these type annotations and will create a Pydantic `TypeAdapter` for them, so types work normally without custom wrapper models.
|
||||
@@ -0,0 +1,79 @@
|
||||
# Responses
|
||||
|
||||
## Return Type or Response Model
|
||||
|
||||
When possible, include a return type. It will be used to validate, filter, document, and serialize the response.
|
||||
|
||||
```python
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
description: str | None = None
|
||||
|
||||
|
||||
@app.get("/items/me")
|
||||
async def get_item() -> Item:
|
||||
return Item(name="Plumbus", description="All-purpose home device")
|
||||
```
|
||||
|
||||
Return types or response models filter data to avoid exposing sensitive information. They also let Pydantic serialize data on the Rust side for performance.
|
||||
|
||||
The return type doesn't have to be a Pydantic model. It can be a different type, like a list of integers, a dict, etc.
|
||||
|
||||
## When to use `response_model`
|
||||
|
||||
If the return type is not the same as the type that you want to use to validate, filter, or serialize, use the `response_model` parameter on the decorator.
|
||||
|
||||
```python
|
||||
from typing import Any
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
description: str | None = None
|
||||
|
||||
|
||||
@app.get("/items/me", response_model=Item)
|
||||
async def get_item() -> Any:
|
||||
return {"name": "Foo", "description": "A very nice Item"}
|
||||
```
|
||||
|
||||
This is particularly useful when filtering data to expose only the public fields and avoid exposing sensitive information.
|
||||
|
||||
```python
|
||||
from typing import Any
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class InternalItem(BaseModel):
|
||||
name: str
|
||||
description: str | None = None
|
||||
secret_key: str
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: str
|
||||
description: str | None = None
|
||||
|
||||
|
||||
@app.get("/items/me", response_model=Item)
|
||||
async def get_item() -> Any:
|
||||
item = InternalItem(
|
||||
name="Foo", description="A very nice Item", secret_key="supersecret"
|
||||
)
|
||||
return item
|
||||
```
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
||||
|
||||
__version__ = "0.138.0"
|
||||
__version__ = "0.138.2"
|
||||
|
||||
from starlette import status as status
|
||||
|
||||
|
||||
+37
-25
@@ -1841,34 +1841,19 @@ class _FrontendStaticFiles(StaticFiles):
|
||||
|
||||
async def get_response(self, path: str, scope: Scope) -> Response:
|
||||
if scope["method"] not in ("GET", "HEAD"):
|
||||
raise HTTPException(status_code=405)
|
||||
if await self._lookup_static_resource(path) is not None:
|
||||
raise HTTPException(status_code=405)
|
||||
raise HTTPException(status_code=404)
|
||||
|
||||
try:
|
||||
full_path, stat_result = await run_in_threadpool(self.lookup_path, path)
|
||||
except PermissionError:
|
||||
raise HTTPException(status_code=401) from None
|
||||
except OSError as exc:
|
||||
if exc.errno == errno.ENAMETOOLONG:
|
||||
raise HTTPException(status_code=404) from None
|
||||
raise exc
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=404) from None
|
||||
|
||||
if stat_result and stat.S_ISREG(stat_result.st_mode):
|
||||
static_resource = await self._lookup_static_resource(path)
|
||||
if static_resource is not None:
|
||||
full_path, stat_result, is_directory_index = static_resource
|
||||
if is_directory_index and not scope["path"].endswith("/"):
|
||||
url = URL(scope=scope)
|
||||
url = url.replace(path=url.path + "/")
|
||||
return RedirectResponse(url=url)
|
||||
return self.file_response(full_path, stat_result, scope)
|
||||
|
||||
if stat_result and stat.S_ISDIR(stat_result.st_mode):
|
||||
index_path = os.path.join(path, "index.html")
|
||||
full_path, stat_result = await run_in_threadpool(
|
||||
self.lookup_path, index_path
|
||||
)
|
||||
if stat_result is not None and stat.S_ISREG(stat_result.st_mode):
|
||||
if not scope["path"].endswith("/"):
|
||||
url = URL(scope=scope)
|
||||
url = url.replace(path=url.path + "/")
|
||||
return RedirectResponse(url=url)
|
||||
return self.file_response(full_path, stat_result, scope)
|
||||
|
||||
if self.fallback == "404.html" or (
|
||||
self.fallback == "auto" and self._fallback_file_exists("404.html")
|
||||
):
|
||||
@@ -1882,6 +1867,33 @@ class _FrontendStaticFiles(StaticFiles):
|
||||
|
||||
raise HTTPException(status_code=404)
|
||||
|
||||
async def _lookup_path(self, path: str) -> tuple[str, os.stat_result | None]:
|
||||
try:
|
||||
return await run_in_threadpool(self.lookup_path, path)
|
||||
except PermissionError:
|
||||
raise HTTPException(status_code=401) from None
|
||||
except OSError as exc:
|
||||
if exc.errno == errno.ENAMETOOLONG:
|
||||
raise HTTPException(status_code=404) from None
|
||||
raise exc
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=404) from None
|
||||
|
||||
async def _lookup_static_resource(
|
||||
self, path: str
|
||||
) -> tuple[str, os.stat_result, bool] | None:
|
||||
full_path, stat_result = await self._lookup_path(path)
|
||||
if stat_result is None:
|
||||
return None
|
||||
if stat.S_ISREG(stat_result.st_mode):
|
||||
return full_path, stat_result, False
|
||||
if stat.S_ISDIR(stat_result.st_mode):
|
||||
index_path = os.path.join(path, "index.html")
|
||||
full_path, stat_result = await self._lookup_path(index_path)
|
||||
if stat_result is not None and stat.S_ISREG(stat_result.st_mode):
|
||||
return full_path, stat_result, True
|
||||
return None
|
||||
|
||||
def _fallback_file_exists(self, fallback: str) -> bool:
|
||||
_, stat_result = self.lookup_path(fallback)
|
||||
return stat_result is not None and stat.S_ISREG(stat_result.st_mode)
|
||||
|
||||
@@ -249,6 +249,7 @@ def stage_zensical_docs(lang: str) -> Path:
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
render_banner_sponsors()
|
||||
shutil.copytree(en_docs_path / "data", lang_stage_path / "data")
|
||||
shutil.copytree(en_docs_path / "overrides", lang_stage_path / "overrides")
|
||||
|
||||
@@ -461,6 +462,7 @@ def live() -> None:
|
||||
"""
|
||||
Serve the English docs with livereload from the source files.
|
||||
"""
|
||||
render_banner_sponsors()
|
||||
subprocess.run(
|
||||
[
|
||||
"zensical",
|
||||
@@ -508,6 +510,56 @@ def get_updated_config_content() -> dict[str, Any]:
|
||||
return config
|
||||
|
||||
|
||||
banner_sponsors_template = """{% for sponsor in banner_sponsors -%}
|
||||
<div class="item">
|
||||
<a title="{{ sponsor.title }}" style="display: block; position: relative;" href="{{ sponsor.url }}" target="_blank">
|
||||
<span class="sponsor-badge">sponsor</span>
|
||||
<img class="sponsor-image" src="{{ sponsor.img }}" alt="{{ sponsor.title }}" />
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
"""
|
||||
|
||||
|
||||
def get_banner_sponsors(sponsors: dict[str, Any]) -> list[dict[str, str]]:
|
||||
banner_sponsors: list[dict[str, str]] = []
|
||||
for sponsor in sponsors.get("gold", []):
|
||||
banner_img = sponsor.get("banner_img")
|
||||
if not banner_img:
|
||||
continue
|
||||
banner_sponsors.append(
|
||||
{
|
||||
"url": sponsor.get("banner_url", sponsor["url"]),
|
||||
"title": sponsor.get("banner_title", sponsor["title"]),
|
||||
"img": banner_img,
|
||||
}
|
||||
)
|
||||
return banner_sponsors
|
||||
|
||||
|
||||
def render_banner_sponsors_partial() -> str:
|
||||
sponsors_path = en_docs_path / "data" / "sponsors.yml"
|
||||
sponsors = yaml.safe_load(sponsors_path.read_text(encoding="utf-8"))
|
||||
template = Template(banner_sponsors_template)
|
||||
return template.render(banner_sponsors=get_banner_sponsors(sponsors))
|
||||
|
||||
|
||||
@app.command()
|
||||
def render_banner_sponsors() -> None:
|
||||
"""
|
||||
Render the sponsor banner partial from sponsors.yml.
|
||||
"""
|
||||
partial_path = en_docs_path / "overrides" / "partials" / "banner-sponsors.html"
|
||||
old_content = partial_path.read_text("utf-8") if partial_path.is_file() else ""
|
||||
new_content = render_banner_sponsors_partial()
|
||||
if new_content != old_content:
|
||||
print(f"{partial_path} outdated from the latest sponsors.yml")
|
||||
print(f"Updating {partial_path}")
|
||||
partial_path.write_text(new_content, encoding="utf-8")
|
||||
raise typer.Exit(1)
|
||||
print(f"{partial_path} is up to date ✅")
|
||||
|
||||
|
||||
@app.command()
|
||||
def ensure_non_translated() -> None:
|
||||
"""
|
||||
|
||||
@@ -2,6 +2,7 @@ import errno
|
||||
import os
|
||||
import runpy
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
|
||||
import anyio
|
||||
import pytest
|
||||
@@ -639,6 +640,21 @@ def test_head_requests_work(tmp_path: Path):
|
||||
assert response.headers["content-length"] == "2"
|
||||
|
||||
|
||||
def test_head_fallback_request_works(tmp_path: Path):
|
||||
dist = tmp_path / "dist"
|
||||
write_file(dist / "index.html", "app shell")
|
||||
app = FastAPI()
|
||||
app.frontend("/", directory=dist, fallback="index.html")
|
||||
|
||||
response = TestClient(app).head(
|
||||
"/dashboard/settings", headers={"accept": "text/html"}
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.text == ""
|
||||
assert response.headers["content-length"] == "9"
|
||||
|
||||
|
||||
def test_unsupported_methods_return_405(tmp_path: Path):
|
||||
dist = tmp_path / "dist"
|
||||
write_file(dist / "asset.txt", "ok")
|
||||
@@ -650,6 +666,125 @@ def test_unsupported_methods_return_405(tmp_path: Path):
|
||||
assert response.status_code == 405
|
||||
|
||||
|
||||
@pytest.mark.parametrize("method", ["POST", "PUT", "PATCH", "DELETE", "OPTIONS"])
|
||||
def test_unsupported_methods_to_fallback_only_routes_return_404(
|
||||
tmp_path: Path, method: str
|
||||
):
|
||||
dist = tmp_path / "dist"
|
||||
write_file(dist / "index.html", "app shell")
|
||||
app = FastAPI()
|
||||
app.frontend("/", directory=dist, fallback="index.html")
|
||||
|
||||
response = TestClient(app).request(
|
||||
method, "/dashboard/settings", headers={"accept": "text/html"}
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_unsupported_methods_to_frontend_root_and_directory_index_return_405(
|
||||
tmp_path: Path,
|
||||
):
|
||||
dist = tmp_path / "dist"
|
||||
write_file(dist / "index.html", "app")
|
||||
write_file(dist / "about" / "index.html", "about")
|
||||
app = FastAPI()
|
||||
app.frontend("/", directory=dist)
|
||||
client = TestClient(app)
|
||||
|
||||
root_response = client.post("/")
|
||||
directory_response = client.post("/about/")
|
||||
|
||||
assert root_response.status_code == 405
|
||||
assert directory_response.status_code == 405
|
||||
|
||||
|
||||
def test_unsupported_method_to_directory_without_index_returns_404(tmp_path: Path):
|
||||
dist = tmp_path / "dist"
|
||||
(dist / "empty").mkdir(parents=True)
|
||||
write_file(dist / "index.html", "app")
|
||||
app = FastAPI()
|
||||
app.frontend("/", directory=dist)
|
||||
|
||||
response = TestClient(app).post("/empty/")
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_unsupported_methods_to_fallback_only_routes_ignore_accept(
|
||||
tmp_path: Path,
|
||||
):
|
||||
dist = tmp_path / "dist"
|
||||
write_file(dist / "index.html", "app shell")
|
||||
app = FastAPI()
|
||||
app.frontend("/", directory=dist, fallback="index.html")
|
||||
|
||||
response = TestClient(app).post(
|
||||
"/dashboard/settings", headers={"accept": "application/json"}
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("fallback", "files"),
|
||||
[
|
||||
("404.html", {"404.html": "missing"}),
|
||||
("auto", {"index.html": "app shell"}),
|
||||
(None, {"index.html": "app shell"}),
|
||||
],
|
||||
)
|
||||
def test_unsupported_methods_to_fallback_only_routes_return_404_for_fallback_modes(
|
||||
tmp_path: Path,
|
||||
fallback: Literal["auto", "index.html", "404.html"] | None,
|
||||
files: dict[str, str],
|
||||
):
|
||||
dist = tmp_path / "dist"
|
||||
for file, content in files.items():
|
||||
write_file(dist / file, content)
|
||||
app = FastAPI()
|
||||
app.frontend("/", directory=dist, fallback=fallback)
|
||||
|
||||
response = TestClient(app).post(
|
||||
"/dashboard/settings", headers={"accept": "text/html"}
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_apirouter_frontend_unsupported_method_to_fallback_only_route_returns_404(
|
||||
tmp_path: Path,
|
||||
):
|
||||
dist = tmp_path / "dist"
|
||||
write_file(dist / "index.html", "admin")
|
||||
router = APIRouter()
|
||||
router.frontend("/", directory=dist, fallback="index.html")
|
||||
app = FastAPI()
|
||||
app.include_router(router, prefix="/admin")
|
||||
|
||||
response = TestClient(app).post(
|
||||
"/admin/client-route", headers={"accept": "text/html"}
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_unsupported_method_uses_longest_matching_frontend_prefix(tmp_path: Path):
|
||||
site = tmp_path / "site"
|
||||
admin = tmp_path / "admin"
|
||||
write_file(site / "admin" / "client-route", "site asset")
|
||||
write_file(admin / "index.html", "admin")
|
||||
app = FastAPI()
|
||||
app.frontend("/", directory=site)
|
||||
app.frontend("/admin", directory=admin, fallback="index.html")
|
||||
|
||||
response = TestClient(app).post(
|
||||
"/admin/client-route", headers={"accept": "text/html"}
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"path",
|
||||
[
|
||||
|
||||
@@ -4103,16 +4103,16 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-settings"
|
||||
version = "2.14.1"
|
||||
version = "2.14.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/07/60/1d1e59c9c90d54591469ada7d268251f71c24bdb765f1a8a832cee8c6653/pydantic_settings-2.14.1.tar.gz", hash = "sha256:e874d3bec7e787b0c9958277956ed9b4dd5de6a80e162188fdaff7c5e26fd5fa", size = 235551, upload-time = "2026-05-08T13:40:06.542Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5c/b5/8f48e906c3e0205276e8bd8cb7512217a87b2685304d64be27cad5b3019f/pydantic_settings-2.14.2.tar.gz", hash = "sha256:c19dd64b19097f1de80184f0cc7b0272a13ae6e170cbf240a3e27e381ed14a5f", size = 237700, upload-time = "2026-06-19T13:44:56.324Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/8d/f1af3832f5e6eb13ba94ee809e72b8ecb5eef226d27ee0bef7d963d943c7/pydantic_settings-2.14.1-py3-none-any.whl", hash = "sha256:6e3c7edfd8277687cdc598f56e5cff0e9bfff0910a3749deaa8d4401c3a2b9de", size = 60964, upload-time = "2026-05-08T13:40:04.958Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/c1/6e422f34e569cf8e18df68d1939c81c099d2b61e4f7d9621c8a77560799c/pydantic_settings-2.14.2-py3-none-any.whl", hash = "sha256:a20c97b37910b6550d5ea50fbcc2d4187defe58cd57070b73863d069419c9440", size = 61715, upload-time = "2026-06-19T13:44:55.02Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in new issue
Block a user