mirror of
https://github.com/fastapi/fastapi.git
synced 2026-09-08 11:35:12 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
702fea83bb | ||
|
|
6466865eec | ||
|
|
b790e14cb6 | ||
|
|
c2708d9817 | ||
|
|
403b1fa16f | ||
|
|
1929ac2319 | ||
|
|
cba4158567 |
No files matched your search
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -7,6 +7,17 @@ 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
|
||||
|
||||
@@ -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
-1
@@ -1,6 +1,6 @@
|
||||
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
||||
|
||||
__version__ = "0.138.1"
|
||||
__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",
|
||||
[
|
||||
|
||||
Reference in new issue
Block a user