committed by
GitHub
33 changed files with 878 additions and 55 deletions
@ -1,16 +1,24 @@ |
|||||
# FastAPI bei Cloudanbietern bereitstellen { #deploy-fastapi-on-cloud-providers } |
# FastAPI bei Cloudanbietern deployen { #deploy-fastapi-on-cloud-providers } |
||||
|
|
||||
Sie können praktisch **jeden Cloudanbieter** verwenden, um Ihre FastAPI-Anwendung bereitzustellen. |
Sie können praktisch **jeden Cloudanbieter** verwenden, um Ihre FastAPI-Anwendung bereitzustellen. |
||||
|
|
||||
In den meisten Fällen bieten die großen Cloudanbieter Anleitungen zum Bereitstellen von FastAPI an. |
In den meisten Fällen bieten die großen Cloudanbieter Anleitungen zum Deployment von FastAPI an. |
||||
|
|
||||
## Cloudanbieter – Sponsoren { #cloud-providers-sponsors } |
## FastAPI Cloud { #fastapi-cloud } |
||||
|
|
||||
|
**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>** wurde vom selben Autor und Team hinter **FastAPI** entwickelt. |
||||
|
|
||||
|
Es vereinfacht den Prozess des **Erstellens**, **Deployens** und **Zugreifens** auf eine API mit minimalem Aufwand. |
||||
|
|
||||
Einige Cloudanbieter ✨ [**sponsern FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, dies stellt die kontinuierliche und gesunde **Entwicklung** von FastAPI und seinem **Ökosystem** sicher. |
Es bringt die gleiche **Developer-Experience** beim Erstellen von Apps mit FastAPI auch zum **Deployment** in der Cloud. 🎉 |
||||
|
|
||||
|
FastAPI Cloud ist der Hauptsponsor und Finanzierungsgeber für die *FastAPI and friends* Open-Source-Projekte. ✨ |
||||
|
|
||||
|
## Cloudanbieter – Sponsoren { #cloud-providers-sponsors } |
||||
|
|
||||
Und es zeigt ihr wahres Engagement für FastAPI und seine **Community** (Sie), da sie Ihnen nicht nur einen **guten Service** bieten möchten, sondern auch sicherstellen möchten, dass Sie ein **gutes und gesundes Framework**, FastAPI, haben. 🙇 |
Einige andere Cloudanbieter ✨ [**sponsern FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ ebenfalls. 🙇 |
||||
|
|
||||
Vielleicht möchten Sie deren Dienste ausprobieren und deren Anleitungen folgen: |
Sie könnten diese ebenfalls in Betracht ziehen, deren Anleitungen folgen und ihre Dienste ausprobieren: |
||||
|
|
||||
* <a href="https://docs.render.com/deploy-fastapi?utm_source=deploydoc&utm_medium=referral&utm_campaign=fastapi" class="external-link" target="_blank">Render</a> |
* <a href="https://docs.render.com/deploy-fastapi?utm_source=deploydoc&utm_medium=referral&utm_campaign=fastapi" class="external-link" target="_blank">Render</a> |
||||
* <a href="https://docs.railway.com/guides/fastapi?utm_medium=integration&utm_source=docs&utm_campaign=fastapi" class="external-link" target="_blank">Railway</a> |
* <a href="https://docs.railway.com/guides/fastapi?utm_medium=integration&utm_source=docs&utm_campaign=fastapi" class="external-link" target="_blank">Railway</a> |
||||
|
|||||
@ -0,0 +1,65 @@ |
|||||
|
# FastAPI Cloud { #fastapi-cloud } |
||||
|
|
||||
|
Sie können Ihre FastAPI-App in der <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a> mit **einem einzigen Befehl** deployen – tragen Sie sich in die Warteliste ein, falls noch nicht geschehen. 🚀 |
||||
|
|
||||
|
## Anmelden { #login } |
||||
|
|
||||
|
Stellen Sie sicher, dass Sie bereits ein **FastAPI-Cloud-Konto** haben (wir haben Sie von der Warteliste eingeladen 😉). |
||||
|
|
||||
|
Melden Sie sich dann an: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ fastapi login |
||||
|
|
||||
|
You are logged in to FastAPI Cloud 🚀 |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
## Deployen { #deploy } |
||||
|
|
||||
|
Stellen Sie Ihre App jetzt mit **einem einzigen Befehl** bereit: |
||||
|
|
||||
|
<div class="termy"> |
||||
|
|
||||
|
```console |
||||
|
$ fastapi deploy |
||||
|
|
||||
|
Deploying to FastAPI Cloud... |
||||
|
|
||||
|
✅ Deployment successful! |
||||
|
|
||||
|
🐔 Ready the chicken! Your app is ready at https://myapp.fastapicloud.dev |
||||
|
``` |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
Das war’s! Jetzt können Sie Ihre App unter dieser URL aufrufen. ✨ |
||||
|
|
||||
|
## Über FastAPI Cloud { #about-fastapi-cloud } |
||||
|
|
||||
|
**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>** wird vom gleichen Autor und Team hinter **FastAPI** entwickelt. |
||||
|
|
||||
|
Es vereinfacht den Prozess des **Erstellens**, **Deployens** und **Nutzens** einer API mit minimalem Aufwand. |
||||
|
|
||||
|
Es bringt die gleiche **Developer-Experience** beim Erstellen von Apps mit FastAPI auch zum **Deployment** in der Cloud. 🎉 |
||||
|
|
||||
|
Es kümmert sich außerdem um das meiste, was beim Deployen einer App nötig ist, zum Beispiel: |
||||
|
|
||||
|
* HTTPS |
||||
|
* Replikation, mit Autoscaling basierend auf Requests |
||||
|
* usw. |
||||
|
|
||||
|
FastAPI Cloud ist Hauptsponsor und Finanzierer der Open-Source-Projekte *FastAPI and friends*. ✨ |
||||
|
|
||||
|
## Bei anderen Cloudanbietern deployen { #deploy-to-other-cloud-providers } |
||||
|
|
||||
|
FastAPI ist Open Source und basiert auf Standards. Sie können FastAPI-Apps bei jedem Cloudanbieter Ihrer Wahl deployen. |
||||
|
|
||||
|
Folgen Sie den Anleitungen Ihres Cloudanbieters, um dort FastAPI-Apps zu deployen. 🤓 |
||||
|
|
||||
|
## Auf den eigenen Server deployen { #deploy-your-own-server } |
||||
|
|
||||
|
Ich werde Ihnen später in diesem **Deployment-Leitfaden** auch alle Details zeigen, sodass Sie verstehen, was passiert, was geschehen muss und wie Sie FastAPI-Apps selbst deployen können, auch auf Ihre eigenen Server. 🤓 |
||||
@ -0,0 +1,17 @@ |
|||||
|
# Alte 403-Authentifizierungsfehler-Statuscodes verwenden { #use-old-403-authentication-error-status-codes } |
||||
|
|
||||
|
Vor FastAPI-Version `0.122.0` verwendeten die integrierten Sicherheits-Utilities den HTTP-Statuscode `403 Forbidden`, wenn sie dem Client nach einer fehlgeschlagenen Authentifizierung einen Fehler zurückgaben. |
||||
|
|
||||
|
Ab FastAPI-Version `0.122.0` verwenden sie den passenderen HTTP-Statuscode `401 Unauthorized` und geben in der Response einen sinnvollen `WWW-Authenticate`-Header zurück, gemäß den HTTP-Spezifikationen, <a href="https://datatracker.ietf.org/doc/html/rfc7235#section-3.1" class="external-link" target="_blank">RFC 7235</a>, <a href="https://datatracker.ietf.org/doc/html/rfc9110#name-401-unauthorized" class="external-link" target="_blank">RFC 9110</a>. |
||||
|
|
||||
|
Aber falls Ihre Clients aus irgendeinem Grund vom alten Verhalten abhängen, können Sie darauf zurückgreifen, indem Sie in Ihren Sicherheitsklassen die Methode `make_not_authenticated_error` überschreiben. |
||||
|
|
||||
|
Sie können beispielsweise eine Unterklasse von `HTTPBearer` erstellen, die einen Fehler `403 Forbidden` zurückgibt, statt des Default-`401 Unauthorized`-Fehlers: |
||||
|
|
||||
|
{* ../../docs_src/authentication_error_status_code/tutorial001_an_py39.py hl[9:13] *} |
||||
|
|
||||
|
/// tip | Tipp |
||||
|
|
||||
|
Beachten Sie, dass die Funktion die Exception-Instanz zurückgibt; sie wirft sie nicht. Das Werfen erfolgt im restlichen internen Code. |
||||
|
|
||||
|
/// |
||||
@ -0,0 +1,9 @@ |
|||||
|
from pydantic import BaseModel |
||||
|
|
||||
|
|
||||
|
def forwardref_method(input: "ForwardRefModel") -> "ForwardRefModel": |
||||
|
return ForwardRefModel(x=input.x + 1) |
||||
|
|
||||
|
|
||||
|
class ForwardRefModel(BaseModel): |
||||
|
x: int = 0 |
||||
@ -0,0 +1,251 @@ |
|||||
|
from functools import partial |
||||
|
from typing import AsyncGenerator, Generator |
||||
|
|
||||
|
import pytest |
||||
|
from fastapi import Depends, FastAPI |
||||
|
from fastapi.testclient import TestClient |
||||
|
from typing_extensions import Annotated |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
def function_dependency(value: str) -> str: |
||||
|
return value |
||||
|
|
||||
|
|
||||
|
async def async_function_dependency(value: str) -> str: |
||||
|
return value |
||||
|
|
||||
|
|
||||
|
def gen_dependency(value: str) -> Generator[str, None, None]: |
||||
|
yield value |
||||
|
|
||||
|
|
||||
|
async def async_gen_dependency(value: str) -> AsyncGenerator[str, None]: |
||||
|
yield value |
||||
|
|
||||
|
|
||||
|
class CallableDependency: |
||||
|
def __call__(self, value: str) -> str: |
||||
|
return value |
||||
|
|
||||
|
|
||||
|
class CallableGenDependency: |
||||
|
def __call__(self, value: str) -> Generator[str, None, None]: |
||||
|
yield value |
||||
|
|
||||
|
|
||||
|
class AsyncCallableDependency: |
||||
|
async def __call__(self, value: str) -> str: |
||||
|
return value |
||||
|
|
||||
|
|
||||
|
class AsyncCallableGenDependency: |
||||
|
async def __call__(self, value: str) -> AsyncGenerator[str, None]: |
||||
|
yield value |
||||
|
|
||||
|
|
||||
|
class MethodsDependency: |
||||
|
def synchronous(self, value: str) -> str: |
||||
|
return value |
||||
|
|
||||
|
async def asynchronous(self, value: str) -> str: |
||||
|
return value |
||||
|
|
||||
|
def synchronous_gen(self, value: str) -> Generator[str, None, None]: |
||||
|
yield value |
||||
|
|
||||
|
async def asynchronous_gen(self, value: str) -> AsyncGenerator[str, None]: |
||||
|
yield value |
||||
|
|
||||
|
|
||||
|
callable_dependency = CallableDependency() |
||||
|
callable_gen_dependency = CallableGenDependency() |
||||
|
async_callable_dependency = AsyncCallableDependency() |
||||
|
async_callable_gen_dependency = AsyncCallableGenDependency() |
||||
|
methods_dependency = MethodsDependency() |
||||
|
|
||||
|
|
||||
|
@app.get("/partial-function-dependency") |
||||
|
async def get_partial_function_dependency( |
||||
|
value: Annotated[ |
||||
|
str, Depends(partial(function_dependency, "partial-function-dependency")) |
||||
|
], |
||||
|
) -> str: |
||||
|
return value |
||||
|
|
||||
|
|
||||
|
@app.get("/partial-async-function-dependency") |
||||
|
async def get_partial_async_function_dependency( |
||||
|
value: Annotated[ |
||||
|
str, |
||||
|
Depends( |
||||
|
partial(async_function_dependency, "partial-async-function-dependency") |
||||
|
), |
||||
|
], |
||||
|
) -> str: |
||||
|
return value |
||||
|
|
||||
|
|
||||
|
@app.get("/partial-gen-dependency") |
||||
|
async def get_partial_gen_dependency( |
||||
|
value: Annotated[str, Depends(partial(gen_dependency, "partial-gen-dependency"))], |
||||
|
) -> str: |
||||
|
return value |
||||
|
|
||||
|
|
||||
|
@app.get("/partial-async-gen-dependency") |
||||
|
async def get_partial_async_gen_dependency( |
||||
|
value: Annotated[ |
||||
|
str, Depends(partial(async_gen_dependency, "partial-async-gen-dependency")) |
||||
|
], |
||||
|
) -> str: |
||||
|
return value |
||||
|
|
||||
|
|
||||
|
@app.get("/partial-callable-dependency") |
||||
|
async def get_partial_callable_dependency( |
||||
|
value: Annotated[ |
||||
|
str, Depends(partial(callable_dependency, "partial-callable-dependency")) |
||||
|
], |
||||
|
) -> str: |
||||
|
return value |
||||
|
|
||||
|
|
||||
|
@app.get("/partial-callable-gen-dependency") |
||||
|
async def get_partial_callable_gen_dependency( |
||||
|
value: Annotated[ |
||||
|
str, |
||||
|
Depends(partial(callable_gen_dependency, "partial-callable-gen-dependency")), |
||||
|
], |
||||
|
) -> str: |
||||
|
return value |
||||
|
|
||||
|
|
||||
|
@app.get("/partial-async-callable-dependency") |
||||
|
async def get_partial_async_callable_dependency( |
||||
|
value: Annotated[ |
||||
|
str, |
||||
|
Depends( |
||||
|
partial(async_callable_dependency, "partial-async-callable-dependency") |
||||
|
), |
||||
|
], |
||||
|
) -> str: |
||||
|
return value |
||||
|
|
||||
|
|
||||
|
@app.get("/partial-async-callable-gen-dependency") |
||||
|
async def get_partial_async_callable_gen_dependency( |
||||
|
value: Annotated[ |
||||
|
str, |
||||
|
Depends( |
||||
|
partial( |
||||
|
async_callable_gen_dependency, "partial-async-callable-gen-dependency" |
||||
|
) |
||||
|
), |
||||
|
], |
||||
|
) -> str: |
||||
|
return value |
||||
|
|
||||
|
|
||||
|
@app.get("/partial-synchronous-method-dependency") |
||||
|
async def get_partial_synchronous_method_dependency( |
||||
|
value: Annotated[ |
||||
|
str, |
||||
|
Depends( |
||||
|
partial( |
||||
|
methods_dependency.synchronous, "partial-synchronous-method-dependency" |
||||
|
) |
||||
|
), |
||||
|
], |
||||
|
) -> str: |
||||
|
return value |
||||
|
|
||||
|
|
||||
|
@app.get("/partial-synchronous-method-gen-dependency") |
||||
|
async def get_partial_synchronous_method_gen_dependency( |
||||
|
value: Annotated[ |
||||
|
str, |
||||
|
Depends( |
||||
|
partial( |
||||
|
methods_dependency.synchronous_gen, |
||||
|
"partial-synchronous-method-gen-dependency", |
||||
|
) |
||||
|
), |
||||
|
], |
||||
|
) -> str: |
||||
|
return value |
||||
|
|
||||
|
|
||||
|
@app.get("/partial-asynchronous-method-dependency") |
||||
|
async def get_partial_asynchronous_method_dependency( |
||||
|
value: Annotated[ |
||||
|
str, |
||||
|
Depends( |
||||
|
partial( |
||||
|
methods_dependency.asynchronous, |
||||
|
"partial-asynchronous-method-dependency", |
||||
|
) |
||||
|
), |
||||
|
], |
||||
|
) -> str: |
||||
|
return value |
||||
|
|
||||
|
|
||||
|
@app.get("/partial-asynchronous-method-gen-dependency") |
||||
|
async def get_partial_asynchronous_method_gen_dependency( |
||||
|
value: Annotated[ |
||||
|
str, |
||||
|
Depends( |
||||
|
partial( |
||||
|
methods_dependency.asynchronous_gen, |
||||
|
"partial-asynchronous-method-gen-dependency", |
||||
|
) |
||||
|
), |
||||
|
], |
||||
|
) -> str: |
||||
|
return value |
||||
|
|
||||
|
|
||||
|
client = TestClient(app) |
||||
|
|
||||
|
|
||||
|
@pytest.mark.parametrize( |
||||
|
"route,value", |
||||
|
[ |
||||
|
("/partial-function-dependency", "partial-function-dependency"), |
||||
|
( |
||||
|
"/partial-async-function-dependency", |
||||
|
"partial-async-function-dependency", |
||||
|
), |
||||
|
("/partial-gen-dependency", "partial-gen-dependency"), |
||||
|
("/partial-async-gen-dependency", "partial-async-gen-dependency"), |
||||
|
("/partial-callable-dependency", "partial-callable-dependency"), |
||||
|
("/partial-callable-gen-dependency", "partial-callable-gen-dependency"), |
||||
|
("/partial-async-callable-dependency", "partial-async-callable-dependency"), |
||||
|
( |
||||
|
"/partial-async-callable-gen-dependency", |
||||
|
"partial-async-callable-gen-dependency", |
||||
|
), |
||||
|
( |
||||
|
"/partial-synchronous-method-dependency", |
||||
|
"partial-synchronous-method-dependency", |
||||
|
), |
||||
|
( |
||||
|
"/partial-synchronous-method-gen-dependency", |
||||
|
"partial-synchronous-method-gen-dependency", |
||||
|
), |
||||
|
( |
||||
|
"/partial-asynchronous-method-dependency", |
||||
|
"partial-asynchronous-method-dependency", |
||||
|
), |
||||
|
( |
||||
|
"/partial-asynchronous-method-gen-dependency", |
||||
|
"partial-asynchronous-method-gen-dependency", |
||||
|
), |
||||
|
], |
||||
|
) |
||||
|
def test_dependency_types_with_partial(route: str, value: str) -> None: |
||||
|
response = client.get(route) |
||||
|
assert response.status_code == 200, response.text |
||||
|
assert response.json() == value |
||||
@ -0,0 +1,77 @@ |
|||||
|
from functools import wraps |
||||
|
from typing import AsyncGenerator, Generator |
||||
|
|
||||
|
import pytest |
||||
|
from fastapi import Depends, FastAPI |
||||
|
from fastapi.testclient import TestClient |
||||
|
|
||||
|
|
||||
|
def noop_wrap(func): |
||||
|
@wraps(func) |
||||
|
def wrapper(*args, **kwargs): |
||||
|
return func(*args, **kwargs) |
||||
|
|
||||
|
return wrapper |
||||
|
|
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
@noop_wrap |
||||
|
def wrapped_dependency() -> bool: |
||||
|
return True |
||||
|
|
||||
|
|
||||
|
@noop_wrap |
||||
|
def wrapped_gen_dependency() -> Generator[bool, None, None]: |
||||
|
yield True |
||||
|
|
||||
|
|
||||
|
@noop_wrap |
||||
|
async def async_wrapped_dependency() -> bool: |
||||
|
return True |
||||
|
|
||||
|
|
||||
|
@noop_wrap |
||||
|
async def async_wrapped_gen_dependency() -> AsyncGenerator[bool, None]: |
||||
|
yield True |
||||
|
|
||||
|
|
||||
|
@app.get("/wrapped-dependency/") |
||||
|
async def get_wrapped_dependency(value: bool = Depends(wrapped_dependency)): |
||||
|
return value |
||||
|
|
||||
|
|
||||
|
@app.get("/wrapped-gen-dependency/") |
||||
|
async def get_wrapped_gen_dependency(value: bool = Depends(wrapped_gen_dependency)): |
||||
|
return value |
||||
|
|
||||
|
|
||||
|
@app.get("/async-wrapped-dependency/") |
||||
|
async def get_async_wrapped_dependency(value: bool = Depends(async_wrapped_dependency)): |
||||
|
return value |
||||
|
|
||||
|
|
||||
|
@app.get("/async-wrapped-gen-dependency/") |
||||
|
async def get_async_wrapped_gen_dependency( |
||||
|
value: bool = Depends(async_wrapped_gen_dependency), |
||||
|
): |
||||
|
return value |
||||
|
|
||||
|
|
||||
|
client = TestClient(app) |
||||
|
|
||||
|
|
||||
|
@pytest.mark.parametrize( |
||||
|
"route", |
||||
|
[ |
||||
|
"/wrapped-dependency", |
||||
|
"/wrapped-gen-dependency", |
||||
|
"/async-wrapped-dependency", |
||||
|
"/async-wrapped-gen-dependency", |
||||
|
], |
||||
|
) |
||||
|
def test_class_dependency(route): |
||||
|
response = client.get(route) |
||||
|
assert response.status_code == 200, response.text |
||||
|
assert response.json() is True |
||||
@ -0,0 +1,30 @@ |
|||||
|
from typing import List, Optional |
||||
|
|
||||
|
from fastapi import FastAPI, File |
||||
|
from fastapi.testclient import TestClient |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
@app.post("/files") |
||||
|
async def upload_files(files: Optional[List[bytes]] = File(None)): |
||||
|
if files is None: |
||||
|
return {"files_count": 0} |
||||
|
return {"files_count": len(files), "sizes": [len(f) for f in files]} |
||||
|
|
||||
|
|
||||
|
def test_optional_bytes_list(): |
||||
|
client = TestClient(app) |
||||
|
response = client.post( |
||||
|
"/files", |
||||
|
files=[("files", b"content1"), ("files", b"content2")], |
||||
|
) |
||||
|
assert response.status_code == 200 |
||||
|
assert response.json() == {"files_count": 2, "sizes": [8, 8]} |
||||
|
|
||||
|
|
||||
|
def test_optional_bytes_list_no_files(): |
||||
|
client = TestClient(app) |
||||
|
response = client.post("/files") |
||||
|
assert response.status_code == 200 |
||||
|
assert response.json() == {"files_count": 0} |
||||
@ -0,0 +1,76 @@ |
|||||
|
from dirty_equals import IsPartialDict |
||||
|
from fastapi import Cookie, FastAPI, Header, Query |
||||
|
from fastapi._compat import PYDANTIC_V2 |
||||
|
from fastapi.testclient import TestClient |
||||
|
from pydantic import BaseModel, Field |
||||
|
|
||||
|
app = FastAPI() |
||||
|
|
||||
|
|
||||
|
class Model(BaseModel): |
||||
|
param: str = Field(alias="param_alias") |
||||
|
|
||||
|
|
||||
|
@app.get("/query") |
||||
|
async def query_model(data: Model = Query()): |
||||
|
return {"param": data.param} |
||||
|
|
||||
|
|
||||
|
@app.get("/header") |
||||
|
async def header_model(data: Model = Header()): |
||||
|
return {"param": data.param} |
||||
|
|
||||
|
|
||||
|
@app.get("/cookie") |
||||
|
async def cookie_model(data: Model = Cookie()): |
||||
|
return {"param": data.param} |
||||
|
|
||||
|
|
||||
|
def test_query_model_with_alias(): |
||||
|
client = TestClient(app) |
||||
|
response = client.get("/query", params={"param_alias": "value"}) |
||||
|
assert response.status_code == 200, response.text |
||||
|
assert response.json() == {"param": "value"} |
||||
|
|
||||
|
|
||||
|
def test_header_model_with_alias(): |
||||
|
client = TestClient(app) |
||||
|
response = client.get("/header", headers={"param_alias": "value"}) |
||||
|
assert response.status_code == 200, response.text |
||||
|
assert response.json() == {"param": "value"} |
||||
|
|
||||
|
|
||||
|
def test_cookie_model_with_alias(): |
||||
|
client = TestClient(app) |
||||
|
client.cookies.set("param_alias", "value") |
||||
|
response = client.get("/cookie") |
||||
|
assert response.status_code == 200, response.text |
||||
|
assert response.json() == {"param": "value"} |
||||
|
|
||||
|
|
||||
|
def test_query_model_with_alias_by_name(): |
||||
|
client = TestClient(app) |
||||
|
response = client.get("/query", params={"param": "value"}) |
||||
|
assert response.status_code == 422, response.text |
||||
|
details = response.json() |
||||
|
if PYDANTIC_V2: |
||||
|
assert details["detail"][0]["input"] == {"param": "value"} |
||||
|
|
||||
|
|
||||
|
def test_header_model_with_alias_by_name(): |
||||
|
client = TestClient(app) |
||||
|
response = client.get("/header", headers={"param": "value"}) |
||||
|
assert response.status_code == 422, response.text |
||||
|
details = response.json() |
||||
|
if PYDANTIC_V2: |
||||
|
assert details["detail"][0]["input"] == IsPartialDict({"param": "value"}) |
||||
|
|
||||
|
|
||||
|
def test_cookie_model_with_alias_by_name(): |
||||
|
client = TestClient(app) |
||||
|
client.cookies.set("param", "value") |
||||
|
response = client.get("/cookie") |
||||
|
assert response.status_code == 422, response.text |
||||
|
details = response.json() |
||||
|
if PYDANTIC_V2: |
||||
|
assert details["detail"][0]["input"] == {"param": "value"} |
||||
@ -0,0 +1,31 @@ |
|||||
|
import functools |
||||
|
|
||||
|
from fastapi import FastAPI |
||||
|
from fastapi.testclient import TestClient |
||||
|
|
||||
|
from .forward_reference_type import forwardref_method |
||||
|
|
||||
|
|
||||
|
def passthrough(f): |
||||
|
@functools.wraps(f) |
||||
|
def method(*args, **kwargs): |
||||
|
return f(*args, **kwargs) |
||||
|
|
||||
|
return method |
||||
|
|
||||
|
|
||||
|
def test_wrapped_method_type_inference(): |
||||
|
""" |
||||
|
Regression test ensuring that when a method imported from another module |
||||
|
is decorated with something that sets the __wrapped__ attribute (functools.wraps), |
||||
|
then the types are still processed correctly, including dereferencing of forward |
||||
|
references. |
||||
|
""" |
||||
|
app = FastAPI() |
||||
|
client = TestClient(app) |
||||
|
app.post("/endpoint")(passthrough(forwardref_method)) |
||||
|
app.post("/endpoint2")(passthrough(passthrough(forwardref_method))) |
||||
|
with client: |
||||
|
response = client.post("/endpoint", json={"input": {"x": 0}}) |
||||
|
response2 = client.post("/endpoint2", json={"input": {"x": 0}}) |
||||
|
assert response.json() == response2.json() == {"x": 1} |
||||
Loading…
Reference in new issue