mirror of https://github.com/tiangolo/fastapi.git
Merge branch 'master' into issue-13399_dont-prefill-form
This commit is contained in:
commit
292c1fdeca
|
|
@ -443,7 +443,7 @@ Für einige sprachspezifische Anweisungen, siehe z. B. den Abschnitt `### Headin
|
||||||
* die Workload
|
* die Workload
|
||||||
|
|
||||||
* das Deployment
|
* das Deployment
|
||||||
* bereitstellen
|
* deployen
|
||||||
|
|
||||||
* das SDK
|
* das SDK
|
||||||
* das Software Development Kit
|
* das Software Development Kit
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@ Dies wurde in Version 0.110.0 geändert, um unbehandelten Speicherverbrauch durc
|
||||||
|
|
||||||
### Hintergrundtasks und Abhängigkeiten mit `yield`, Technische Details { #background-tasks-and-dependencies-with-yield-technical-details }
|
### Hintergrundtasks und Abhängigkeiten mit `yield`, Technische Details { #background-tasks-and-dependencies-with-yield-technical-details }
|
||||||
|
|
||||||
Vor FastAPI 0.106.0 war das Werfen von Exceptions nach `yield` nicht möglich, der Exit-Code in Abhängigkeiten mit `yield` wurde ausgeführt, nachdem die Response gesendet wurde, sodass [Exceptionhandler](../handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} bereits ausgeführt worden wären.
|
Vor FastAPI 0.106.0 war das Werfen von Exceptions nach `yield` nicht möglich, der Exit-Code in Abhängigkeiten mit `yield` wurde ausgeführt, nachdem die Response gesendet wurde, sodass [Exceptionhandler](../tutorial/handling-errors.md#install-custom-exception-handlers){.internal-link target=_blank} bereits ausgeführt worden wären.
|
||||||
|
|
||||||
Dies war so designt, hauptsächlich um die Verwendung derselben von Abhängigkeiten „geyieldeten“ Objekte in Hintergrundtasks zu ermöglichen, da der Exit-Code erst ausgeführt wurde, nachdem die Hintergrundtasks abgeschlossen waren.
|
Dies war so designt, hauptsächlich um die Verwendung derselben von Abhängigkeiten „geyieldeten“ Objekte in Hintergrundtasks zu ermöglichen, da der Exit-Code erst ausgeführt wurde, nachdem die Hintergrundtasks abgeschlossen waren.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ Wenn Sie mehr über HTTPS erfahren möchten, lesen Sie den Leitfaden [Über HTTP
|
||||||
|
|
||||||
///
|
///
|
||||||
|
|
||||||
### Wie Proxy-Forwarded-Header funktionieren
|
### Wie Proxy-Forwarded-Header funktionieren { #how-proxy-forwarded-headers-work }
|
||||||
|
|
||||||
Hier ist eine visuelle Darstellung, wie der **Proxy** weitergeleitete Header zwischen dem Client und dem **Anwendungsserver** hinzufügt:
|
Hier ist eine visuelle Darstellung, wie der **Proxy** weitergeleitete Header zwischen dem Client und dem **Anwendungsserver** hinzufügt:
|
||||||
|
|
||||||
|
|
@ -228,7 +228,7 @@ Die Übergabe des `root_path` an `FastAPI` wäre das Äquivalent zur Übergabe d
|
||||||
|
|
||||||
Beachten Sie, dass der Server (Uvicorn) diesen `root_path` für nichts anderes verwendet als für die Weitergabe an die Anwendung.
|
Beachten Sie, dass der Server (Uvicorn) diesen `root_path` für nichts anderes verwendet als für die Weitergabe an die Anwendung.
|
||||||
|
|
||||||
Aber wenn Sie mit Ihrem Browser auf <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000/app</a> gehen, sehen Sie die normale Response:
|
Aber wenn Sie mit Ihrem Browser auf <a href="http://127.0.0.1:8000/app" class="external-link" target="_blank">http://127.0.0.1:8000/app</a> gehen, sehen Sie die normale Response:
|
||||||
|
|
||||||
```JSON
|
```JSON
|
||||||
{
|
{
|
||||||
|
|
@ -443,6 +443,14 @@ Die Dokumentationsoberfläche interagiert mit dem von Ihnen ausgewählten Server
|
||||||
|
|
||||||
///
|
///
|
||||||
|
|
||||||
|
/// note | Technische Details
|
||||||
|
|
||||||
|
Die Eigenschaft `servers` in der OpenAPI-Spezifikation ist optional.
|
||||||
|
|
||||||
|
Wenn Sie den Parameter `servers` nicht angeben und `root_path` den Wert `/` hat, wird die Eigenschaft `servers` im generierten OpenAPI-Schema standardmäßig vollständig weggelassen, was dem Äquivalent eines einzelnen Servers mit einem `url`-Wert von `/` entspricht.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
### Den automatischen Server von `root_path` deaktivieren { #disable-automatic-server-from-root-path }
|
### Den automatischen Server von `root_path` deaktivieren { #disable-automatic-server-from-root-path }
|
||||||
|
|
||||||
Wenn Sie nicht möchten, dass **FastAPI** einen automatischen Server inkludiert, welcher `root_path` verwendet, können Sie den Parameter `root_path_in_servers=False` verwenden:
|
Wenn Sie nicht möchten, dass **FastAPI** einen automatischen Server inkludiert, welcher `root_path` verwendet, können Sie den Parameter `root_path_in_servers=False` verwenden:
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
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 }
|
## Cloudanbieter – Sponsoren { #cloud-providers-sponsors }
|
||||||
|
|
||||||
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.
|
Einige andere Cloudanbieter ✨ [**sponsern FastAPI**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨ ebenfalls. 🙇
|
||||||
|
|
||||||
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. 🙇
|
Sie könnten diese ebenfalls in Betracht ziehen, deren Anleitungen folgen und ihre Dienste ausprobieren:
|
||||||
|
|
||||||
Vielleicht möchten Sie deren Dienste ausprobieren und deren Anleitungen folgen:
|
|
||||||
|
|
||||||
* <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>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Deployment-Konzepte { #deployments-concepts }
|
# Deployment-Konzepte { #deployments-concepts }
|
||||||
|
|
||||||
Bei dem Deployment – der Bereitstellung – einer **FastAPI**-Anwendung, oder eigentlich jeder Art von Web-API, gibt es mehrere Konzepte, die Sie wahrscheinlich interessieren, und mithilfe der Sie die **am besten geeignete** Methode zur **Bereitstellung Ihrer Anwendung** finden können.
|
Bei dem Deployment – der Bereitstellung – einer **FastAPI**-Anwendung, oder eigentlich jeder Art von Web-API, gibt es mehrere Konzepte, die Sie wahrscheinlich interessieren, und mithilfe der Sie die **am besten geeignete** Methode zum **Deployment Ihrer Anwendung** finden können.
|
||||||
|
|
||||||
Einige wichtige Konzepte sind:
|
Einige wichtige Konzepte sind:
|
||||||
|
|
||||||
|
|
@ -15,11 +15,11 @@ Wir werden sehen, wie diese sich auf das **Deployment** auswirken.
|
||||||
|
|
||||||
Letztendlich besteht das ultimative Ziel darin, **Ihre API-Clients** auf **sichere** Weise zu versorgen, um **Unterbrechungen** zu vermeiden und die **Rechenressourcen** (z. B. entfernte Server/virtuelle Maschinen) so effizient wie möglich zu nutzen. 🚀
|
Letztendlich besteht das ultimative Ziel darin, **Ihre API-Clients** auf **sichere** Weise zu versorgen, um **Unterbrechungen** zu vermeiden und die **Rechenressourcen** (z. B. entfernte Server/virtuelle Maschinen) so effizient wie möglich zu nutzen. 🚀
|
||||||
|
|
||||||
Ich erzähle Ihnen hier etwas mehr über diese **Konzepte**, was Ihnen hoffentlich die **Intuition** gibt, die Sie benötigen, um zu entscheiden, wie Sie Ihre API in sehr unterschiedlichen Umgebungen bereitstellen, möglicherweise sogar in **zukünftigen**, die jetzt noch nicht existieren.
|
Ich erzähle Ihnen hier etwas mehr über diese **Konzepte**, was Ihnen hoffentlich die **Intuition** gibt, die Sie benötigen, um zu entscheiden, wie Sie Ihre API in sehr unterschiedlichen Umgebungen deployen, möglicherweise sogar in **zukünftigen**, die jetzt noch nicht existieren.
|
||||||
|
|
||||||
Durch die Berücksichtigung dieser Konzepte können Sie die beste Variante der Bereitstellung **Ihrer eigenen APIs** **evaluieren und konzipieren**.
|
Durch die Berücksichtigung dieser Konzepte können Sie die beste Variante des Deployments **Ihrer eigenen APIs** **evaluieren und konzipieren**.
|
||||||
|
|
||||||
In den nächsten Kapiteln werde ich Ihnen mehr **konkrete Rezepte** für die Bereitstellung von FastAPI-Anwendungen geben.
|
In den nächsten Kapiteln werde ich Ihnen mehr **konkrete Rezepte** für das Deployment von FastAPI-Anwendungen geben.
|
||||||
|
|
||||||
Aber schauen wir uns zunächst einmal diese grundlegenden **konzeptionellen Ideen** an. Diese Konzepte gelten auch für jede andere Art von Web-API. 💡
|
Aber schauen wir uns zunächst einmal diese grundlegenden **konzeptionellen Ideen** an. Diese Konzepte gelten auch für jede andere Art von Web-API. 💡
|
||||||
|
|
||||||
|
|
@ -271,7 +271,7 @@ In diesem Fall müssen Sie sich darüber keine Sorgen machen. 🤷
|
||||||
|
|
||||||
### Beispiele für Strategien für Vorab-Schritte { #examples-of-previous-steps-strategies }
|
### Beispiele für Strategien für Vorab-Schritte { #examples-of-previous-steps-strategies }
|
||||||
|
|
||||||
Es hängt **stark** davon ab, wie Sie **Ihr System bereitstellen**, und hängt wahrscheinlich mit der Art und Weise zusammen, wie Sie Programme starten, Neustarts durchführen, usw.
|
Es hängt **stark** davon ab, wie Sie **Ihr System deployen**, und hängt wahrscheinlich mit der Art und Weise zusammen, wie Sie Programme starten, Neustarts durchführen, usw.
|
||||||
|
|
||||||
Hier sind einige mögliche Ideen:
|
Hier sind einige mögliche Ideen:
|
||||||
|
|
||||||
|
|
@ -307,7 +307,7 @@ Sie können einfache Tools wie `htop` verwenden, um die in Ihrem Server verwende
|
||||||
|
|
||||||
## Zusammenfassung { #recap }
|
## Zusammenfassung { #recap }
|
||||||
|
|
||||||
Sie haben hier einige der wichtigsten Konzepte gelesen, die Sie wahrscheinlich berücksichtigen müssen, wenn Sie entscheiden, wie Sie Ihre Anwendung bereitstellen:
|
Sie haben hier einige der wichtigsten Konzepte gelesen, die Sie wahrscheinlich berücksichtigen müssen, wenn Sie entscheiden, wie Sie Ihre Anwendung deployen:
|
||||||
|
|
||||||
* Sicherheit – HTTPS
|
* Sicherheit – HTTPS
|
||||||
* Beim Hochfahren ausführen
|
* Beim Hochfahren ausführen
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# FastAPI in Containern – Docker { #fastapi-in-containers-docker }
|
# FastAPI in Containern – Docker { #fastapi-in-containers-docker }
|
||||||
|
|
||||||
Beim Deployment von FastAPI-Anwendungen besteht ein gängiger Ansatz darin, ein **Linux-Containerimage** zu erstellen. Normalerweise erfolgt dies mit <a href="https://www.docker.com/" class="external-link" target="_blank">**Docker**</a>. Sie können dieses Containerimage dann auf eine von mehreren möglichen Arten bereitstellen.
|
Beim Deployment von FastAPI-Anwendungen besteht ein gängiger Ansatz darin, ein **Linux-Containerimage** zu erstellen. Normalerweise erfolgt dies mit <a href="https://www.docker.com/" class="external-link" target="_blank">**Docker**</a>. Sie können dieses Containerimage dann auf eine von mehreren möglichen Arten deployen.
|
||||||
|
|
||||||
Die Verwendung von Linux-Containern bietet mehrere Vorteile, darunter **Sicherheit**, **Replizierbarkeit**, **Einfachheit** und andere.
|
Die Verwendung von Linux-Containern bietet mehrere Vorteile, darunter **Sicherheit**, **Replizierbarkeit**, **Einfachheit** und andere.
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ Linux-Container werden mit demselben Linux-Kernel des Hosts (Maschine, virtuelle
|
||||||
|
|
||||||
Auf diese Weise verbrauchen Container **wenig Ressourcen**, eine Menge vergleichbar mit der direkten Ausführung der Prozesse (eine virtuelle Maschine würde viel mehr verbrauchen).
|
Auf diese Weise verbrauchen Container **wenig Ressourcen**, eine Menge vergleichbar mit der direkten Ausführung der Prozesse (eine virtuelle Maschine würde viel mehr verbrauchen).
|
||||||
|
|
||||||
Container verfügen außerdem über ihre eigenen **isoliert** laufenden Prozesse (üblicherweise nur einen Prozess), über ihr eigenes Dateisystem und ihr eigenes Netzwerk, was die Bereitstellung, Sicherheit, Entwicklung usw. vereinfacht.
|
Container verfügen außerdem über ihre eigenen **isoliert** laufenden Prozesse (üblicherweise nur einen Prozess), über ihr eigenes Dateisystem und ihr eigenes Netzwerk, was Deployment, Sicherheit, Entwicklung usw. vereinfacht.
|
||||||
|
|
||||||
## Was ist ein Containerimage { #what-is-a-container-image }
|
## Was ist ein Containerimage { #what-is-a-container-image }
|
||||||
|
|
||||||
|
|
@ -598,7 +598,7 @@ Zum Beispiel:
|
||||||
* Mit einem **Kubernetes**-Cluster
|
* Mit einem **Kubernetes**-Cluster
|
||||||
* Mit einem Docker Swarm Mode-Cluster
|
* Mit einem Docker Swarm Mode-Cluster
|
||||||
* Mit einem anderen Tool wie Nomad
|
* Mit einem anderen Tool wie Nomad
|
||||||
* Mit einem Cloud-Dienst, der Ihr Containerimage nimmt und es bereitstellt
|
* Mit einem Cloud-Dienst, der Ihr Containerimage nimmt und es deployt
|
||||||
|
|
||||||
## Docker-Image mit `uv` { #docker-image-with-uv }
|
## Docker-Image mit `uv` { #docker-image-with-uv }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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. 🤓
|
||||||
|
|
@ -14,7 +14,9 @@ Das steht im Gegensatz zu den **Entwicklungsphasen**, in denen Sie ständig den
|
||||||
|
|
||||||
Es gibt mehrere Möglichkeiten, dies zu tun, abhängig von Ihrem spezifischen Anwendungsfall und den von Ihnen verwendeten Tools.
|
Es gibt mehrere Möglichkeiten, dies zu tun, abhängig von Ihrem spezifischen Anwendungsfall und den von Ihnen verwendeten Tools.
|
||||||
|
|
||||||
Sie könnten mithilfe einer Kombination von Tools selbst **einen Server bereitstellen**, Sie könnten einen **Cloud-Dienst** nutzen, der einen Teil der Arbeit für Sie erledigt, oder andere mögliche Optionen.
|
Sie könnten mithilfe einer Kombination von Tools selbst **einen Server deployen**, Sie könnten einen **Cloud-Dienst** nutzen, der einen Teil der Arbeit für Sie erledigt, oder andere mögliche Optionen.
|
||||||
|
|
||||||
|
Zum Beispiel haben wir, das Team hinter FastAPI, <a href="https://fastapicloud.com" class="external-link" target="_blank">**FastAPI Cloud**</a> entwickelt, um das Deployment von FastAPI-Apps in der Cloud so reibungslos wie möglich zu gestalten, mit derselben Developer-Experience wie beim Arbeiten mit FastAPI.
|
||||||
|
|
||||||
Ich zeige Ihnen einige der wichtigsten Konzepte, die Sie beim Deployment einer **FastAPI**-Anwendung wahrscheinlich berücksichtigen sollten (obwohl das meiste davon auch für jede andere Art von Webanwendung gilt).
|
Ich zeige Ihnen einige der wichtigsten Konzepte, die Sie beim Deployment einer **FastAPI**-Anwendung wahrscheinlich berücksichtigen sollten (obwohl das meiste davon auch für jede andere Art von Webanwendung gilt).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ Schauen wir uns die Deployment-Konzepte von früher noch einmal an:
|
||||||
|
|
||||||
Bis zu diesem Punkt, in allen Tutorials in der Dokumentation, haben Sie wahrscheinlich ein **Serverprogramm** ausgeführt, zum Beispiel mit dem `fastapi`-Befehl, der Uvicorn startet, und einen **einzelnen Prozess** ausführt.
|
Bis zu diesem Punkt, in allen Tutorials in der Dokumentation, haben Sie wahrscheinlich ein **Serverprogramm** ausgeführt, zum Beispiel mit dem `fastapi`-Befehl, der Uvicorn startet, und einen **einzelnen Prozess** ausführt.
|
||||||
|
|
||||||
Wenn Sie Anwendungen bereitstellen, möchten Sie wahrscheinlich eine gewisse **Replikation von Prozessen**, um **mehrere Kerne** zu nutzen und mehr <abbr title="Request – Anfrage: Daten, die der Client zum Server sendet">Requests</abbr> bearbeiten zu können.
|
Wenn Sie Anwendungen deployen, möchten Sie wahrscheinlich eine gewisse **Replikation von Prozessen**, um **mehrere Kerne** zu nutzen und mehr <abbr title="Request – Anfrage: Daten, die der Client zum Server sendet">Requests</abbr> bearbeiten zu können.
|
||||||
|
|
||||||
Wie Sie im vorherigen Kapitel über [Deployment-Konzepte](concepts.md){.internal-link target=_blank} gesehen haben, gibt es mehrere Strategien, die Sie anwenden können.
|
Wie Sie im vorherigen Kapitel über [Deployment-Konzepte](concepts.md){.internal-link target=_blank} gesehen haben, gibt es mehrere Strategien, die Sie anwenden können.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ Das Ausführen von `fastapi run` startet FastAPI standardmäßig im Produktionsm
|
||||||
|
|
||||||
Standardmäßig ist **Autoreload** deaktiviert. Es horcht auch auf der IP-Adresse `0.0.0.0`, was alle verfügbaren IP-Adressen bedeutet, so wird es öffentlich zugänglich für jeden, der mit der Maschine kommunizieren kann. So würden Sie es normalerweise in der Produktion ausführen, beispielsweise in einem Container.
|
Standardmäßig ist **Autoreload** deaktiviert. Es horcht auch auf der IP-Adresse `0.0.0.0`, was alle verfügbaren IP-Adressen bedeutet, so wird es öffentlich zugänglich für jeden, der mit der Maschine kommunizieren kann. So würden Sie es normalerweise in der Produktion ausführen, beispielsweise in einem Container.
|
||||||
|
|
||||||
In den meisten Fällen würden (und sollten) Sie einen „Terminierungsproxy“ haben, der HTTPS für Sie verwaltet. Dies hängt davon ab, wie Sie Ihre Anwendung bereitstellen. Ihr Anbieter könnte dies für Sie erledigen, oder Sie müssen es selbst einrichten.
|
In den meisten Fällen würden (und sollten) Sie einen „Terminierungsproxy“ haben, der HTTPS für Sie verwaltet. Dies hängt davon ab, wie Sie Ihre Anwendung deployen. Ihr Anbieter könnte dies für Sie erledigen, oder Sie müssen es selbst einrichten.
|
||||||
|
|
||||||
/// tip | Tipp
|
/// tip | Tipp
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
@ -46,20 +46,26 @@ Seine Schlüssel-Merkmale sind:
|
||||||
* **Robust**: Erhalten Sie produktionsreifen Code. Mit automatischer, interaktiver Dokumentation.
|
* **Robust**: Erhalten Sie produktionsreifen Code. Mit automatischer, interaktiver Dokumentation.
|
||||||
* **Standards-basiert**: Basierend auf (und vollständig kompatibel mit) den offenen Standards für APIs: <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> (früher bekannt als Swagger) und <a href="https://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>.
|
* **Standards-basiert**: Basierend auf (und vollständig kompatibel mit) den offenen Standards für APIs: <a href="https://github.com/OAI/OpenAPI-Specification" class="external-link" target="_blank">OpenAPI</a> (früher bekannt als Swagger) und <a href="https://json-schema.org/" class="external-link" target="_blank">JSON Schema</a>.
|
||||||
|
|
||||||
<small>* Schätzung basierend auf Tests in einem internen Entwicklungsteam, das Produktionsanwendungen erstellt.</small>
|
<small>* Schätzung basierend auf Tests, die von einem internen Entwicklungsteam durchgeführt wurden, das Produktionsanwendungen erstellt.</small>
|
||||||
|
|
||||||
## Sponsoren { #sponsors }
|
## Sponsoren { #sponsors }
|
||||||
|
|
||||||
<!-- sponsors -->
|
<!-- sponsors -->
|
||||||
|
|
||||||
{% if sponsors %}
|
### Keystone-Sponsor
|
||||||
|
|
||||||
|
{% for sponsor in sponsors.keystone -%}
|
||||||
|
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
|
||||||
|
{% endfor -%}
|
||||||
|
|
||||||
|
### Gold- und Silber-Sponsoren
|
||||||
|
|
||||||
{% for sponsor in sponsors.gold -%}
|
{% for sponsor in sponsors.gold -%}
|
||||||
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
|
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
|
||||||
{% endfor -%}
|
{% endfor -%}
|
||||||
{%- for sponsor in sponsors.silver -%}
|
{%- for sponsor in sponsors.silver -%}
|
||||||
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
|
<a href="{{ sponsor.url }}" target="_blank" title="{{ sponsor.title }}"><img src="{{ sponsor.img }}" style="border-radius:15px"></a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<!-- /sponsors -->
|
<!-- /sponsors -->
|
||||||
|
|
||||||
|
|
@ -444,6 +450,58 @@ Für ein vollständigeres Beispiel, mit weiteren Funktionen, siehe das <a href="
|
||||||
* **Cookie-Sessions**
|
* **Cookie-Sessions**
|
||||||
* ... und mehr.
|
* ... und mehr.
|
||||||
|
|
||||||
|
### Ihre App deployen (optional) { #deploy-your-app-optional }
|
||||||
|
|
||||||
|
Optional können Sie Ihre FastAPI-App in die <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a> deployen, treten Sie der Warteliste bei, falls noch nicht geschehen. 🚀
|
||||||
|
|
||||||
|
Wenn Sie bereits ein **FastAPI Cloud**-Konto haben (wir haben Sie von der Warteliste eingeladen 😉), können Sie Ihre Anwendung mit einem einzigen Befehl deployen.
|
||||||
|
|
||||||
|
Stellen Sie vor dem Deployen sicher, dass Sie eingeloggt sind:
|
||||||
|
|
||||||
|
<div class="termy">
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ fastapi login
|
||||||
|
|
||||||
|
You are logged in to FastAPI Cloud 🚀
|
||||||
|
```
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Stellen Sie dann Ihre App 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 unter dieser URL auf Ihre App zugreifen. ✨
|
||||||
|
|
||||||
|
#### Über FastAPI Cloud { #about-fastapi-cloud }
|
||||||
|
|
||||||
|
**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>** wird vom selben Autor und Team hinter **FastAPI** entwickelt.
|
||||||
|
|
||||||
|
Es vereinfacht den Prozess des **Erstellens**, **Deployens** und **Zugreifens** auf eine API mit minimalem Aufwand.
|
||||||
|
|
||||||
|
Es bringt die gleiche **Developer-Experience** beim Erstellen von Apps mit FastAPI auch zum **Deployment** in der Cloud. 🎉
|
||||||
|
|
||||||
|
FastAPI Cloud ist der Hauptsponsor und Finanzierer der „FastAPI and friends“ Open-Source-Projekte. ✨
|
||||||
|
|
||||||
|
#### 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 FastAPI-Apps dort bereitzustellen. 🤓
|
||||||
|
|
||||||
## Performanz { #performance }
|
## Performanz { #performance }
|
||||||
|
|
||||||
Unabhängige TechEmpower-Benchmarks zeigen **FastAPI**-Anwendungen, die unter Uvicorn laufen, als <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">eines der schnellsten verfügbaren Python-Frameworks</a>, nur hinter Starlette und Uvicorn selbst (intern von FastAPI verwendet). (*)
|
Unabhängige TechEmpower-Benchmarks zeigen **FastAPI**-Anwendungen, die unter Uvicorn laufen, als <a href="https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7" class="external-link" target="_blank">eines der schnellsten verfügbaren Python-Frameworks</a>, nur hinter Starlette und Uvicorn selbst (intern von FastAPI verwendet). (*)
|
||||||
|
|
|
||||||
|
|
@ -25,4 +25,4 @@ GitHub-Repository: <a href="https://github.com/tiangolo/full-stack-fastapi-templ
|
||||||
- ✅ Tests mit [Pytest](https://pytest.org).
|
- ✅ Tests mit [Pytest](https://pytest.org).
|
||||||
- 📞 [Traefik](https://traefik.io) als Reverse-Proxy / Load Balancer.
|
- 📞 [Traefik](https://traefik.io) als Reverse-Proxy / Load Balancer.
|
||||||
- 🚢 Deployment-Anleitungen unter Verwendung von Docker Compose, einschließlich der Einrichtung eines Frontend-Traefik-Proxys zur Handhabung automatischer HTTPS-Zertifikate.
|
- 🚢 Deployment-Anleitungen unter Verwendung von Docker Compose, einschließlich der Einrichtung eines Frontend-Traefik-Proxys zur Handhabung automatischer HTTPS-Zertifikate.
|
||||||
- 🏭 CI (kontinuierliche Integration) und CD (kontinuierliche Bereitstellung) basierend auf GitHub Actions.
|
- 🏭 CI (kontinuierliche Integration) und CD (kontinuierliches Deployment) basierend auf GitHub Actions.
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,42 @@ Es gibt dutzende Alternativen, die alle auf OpenAPI basieren. Sie können jede d
|
||||||
|
|
||||||
Ebenfalls können Sie es verwenden, um automatisch Code für Clients zu generieren, die mit Ihrer API kommunizieren. Zum Beispiel für Frontend-, Mobile- oder IoT-Anwendungen.
|
Ebenfalls können Sie es verwenden, um automatisch Code für Clients zu generieren, die mit Ihrer API kommunizieren. Zum Beispiel für Frontend-, Mobile- oder IoT-Anwendungen.
|
||||||
|
|
||||||
|
### Ihre App deployen (optional) { #deploy-your-app-optional }
|
||||||
|
|
||||||
|
Sie können optional Ihre FastAPI-App in der <a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a> deployen, treten Sie der Warteliste bei, falls Sie es noch nicht getan haben. 🚀
|
||||||
|
|
||||||
|
Wenn Sie bereits ein **FastAPI Cloud**-Konto haben (wir haben Sie von der Warteliste eingeladen 😉), können Sie Ihre Anwendung mit einem Befehl deployen.
|
||||||
|
|
||||||
|
Vor dem Deployen, stellen Sie sicher, dass Sie eingeloggt sind:
|
||||||
|
|
||||||
|
<div class="termy">
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ fastapi login
|
||||||
|
|
||||||
|
You are logged in to FastAPI Cloud 🚀
|
||||||
|
```
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Dann stellen Sie Ihre App 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. ✨
|
||||||
|
|
||||||
## Zusammenfassung, Schritt für Schritt { #recap-step-by-step }
|
## Zusammenfassung, Schritt für Schritt { #recap-step-by-step }
|
||||||
|
|
||||||
### Schritt 1: `FastAPI` importieren { #step-1-import-fastapi }
|
### Schritt 1: `FastAPI` importieren { #step-1-import-fastapi }
|
||||||
|
|
@ -314,6 +350,26 @@ Sie können auch Pydantic-Modelle zurückgeben (dazu später mehr).
|
||||||
|
|
||||||
Es gibt viele andere Objekte und Modelle, die automatisch zu JSON konvertiert werden (einschließlich ORMs, usw.). Versuchen Sie, Ihre Lieblingsobjekte zu verwenden. Es ist sehr wahrscheinlich, dass sie bereits unterstützt werden.
|
Es gibt viele andere Objekte und Modelle, die automatisch zu JSON konvertiert werden (einschließlich ORMs, usw.). Versuchen Sie, Ihre Lieblingsobjekte zu verwenden. Es ist sehr wahrscheinlich, dass sie bereits unterstützt werden.
|
||||||
|
|
||||||
|
### Schritt 6: Deployen { #step-6-deploy-it }
|
||||||
|
|
||||||
|
Stellen Sie Ihre App in der **<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>** mit einem Befehl bereit: `fastapi deploy`. 🎉
|
||||||
|
|
||||||
|
#### Über FastAPI Cloud { #about-fastapi-cloud }
|
||||||
|
|
||||||
|
**<a href="https://fastapicloud.com" class="external-link" target="_blank">FastAPI Cloud</a>** wird vom selben Autor und Team hinter **FastAPI** entwickelt.
|
||||||
|
|
||||||
|
Es vereinfacht den Prozess des Erstellens, Deployens und des Zugriffs auf eine API mit minimalem Aufwand.
|
||||||
|
|
||||||
|
Es bringt die gleiche **Developer-Experience** beim Erstellen von Apps mit FastAPI auch zum **Deployment** in der Cloud. 🎉
|
||||||
|
|
||||||
|
FastAPI Cloud ist der Hauptsponsor und Finanzierer der „FastAPI and friends“ Open-Source-Projekte. ✨
|
||||||
|
|
||||||
|
#### Zu 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 bereitzustellen. 🤓
|
||||||
|
|
||||||
## Zusammenfassung { #recap }
|
## Zusammenfassung { #recap }
|
||||||
|
|
||||||
* Importieren Sie `FastAPI`.
|
* Importieren Sie `FastAPI`.
|
||||||
|
|
@ -321,3 +377,4 @@ Es gibt viele andere Objekte und Modelle, die automatisch zu JSON konvertiert we
|
||||||
* Schreiben Sie einen **Pfadoperation-Dekorator** unter Verwendung von Dekoratoren wie `@app.get("/")`.
|
* Schreiben Sie einen **Pfadoperation-Dekorator** unter Verwendung von Dekoratoren wie `@app.get("/")`.
|
||||||
* Definieren Sie eine **Pfadoperation-Funktion**, zum Beispiel `def root(): ...`.
|
* Definieren Sie eine **Pfadoperation-Funktion**, zum Beispiel `def root(): ...`.
|
||||||
* Starten Sie den Entwicklungsserver mit dem Befehl `fastapi dev`.
|
* Starten Sie den Entwicklungsserver mit dem Befehl `fastapi dev`.
|
||||||
|
* Optional: Ihre App mit `fastapi deploy` deployen.
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ Es gibt ein paar Unterschiede:
|
||||||
|
|
||||||
* `Field(primary_key=True)` sagt SQLModel, dass die `id` der **Primärschlüssel** in der SQL-Datenbank ist (Sie können mehr über SQL-Primärschlüssel in der SQLModel-Dokumentation erfahren).
|
* `Field(primary_key=True)` sagt SQLModel, dass die `id` der **Primärschlüssel** in der SQL-Datenbank ist (Sie können mehr über SQL-Primärschlüssel in der SQLModel-Dokumentation erfahren).
|
||||||
|
|
||||||
Durch das Festlegen des Typs als `int | None` wird SQLModel wissen, dass diese Spalte ein `INTEGER` in der SQL-Datenbank sein sollte und dass sie `NULLABLE` sein sollte.
|
**Hinweis:** Wir verwenden für das Primärschlüsselfeld `int | None`, damit wir im Python-Code *ein Objekt ohne `id` erstellen* können (`id=None`), in der Annahme, dass die Datenbank sie *beim Speichern generiert*. SQLModel versteht, dass die Datenbank die `id` bereitstellt, und *definiert die Spalte im Datenbankschema als ein Nicht-Null-`INTEGER`*. Siehe die <a href="https://sqlmodel.tiangolo.com/tutorial/create-db-and-table/#primary-key-id" class="external-link" target="_blank">SQLModel-Dokumentation zu Primärschlüsseln</a> für Details.
|
||||||
|
|
||||||
* `Field(index=True)` sagt SQLModel, dass es einen **SQL-Index** für diese Spalte erstellen soll, was schnelleres Suchen in der Datenbank ermöglicht, wenn Daten mittels dieser Spalte gefiltert werden.
|
* `Field(index=True)` sagt SQLModel, dass es einen **SQL-Index** für diese Spalte erstellen soll, was schnelleres Suchen in der Datenbank ermöglicht, wenn Daten mittels dieser Spalte gefiltert werden.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -242,6 +242,26 @@ $ python -m pip install --upgrade pip
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
/// tip | Tipp
|
||||||
|
|
||||||
|
Manchmal kann beim Versuch, `pip` zu aktualisieren, der Fehler **`No module named pip`** auftreten.
|
||||||
|
|
||||||
|
Wenn das passiert, installieren und aktualisieren Sie `pip` mit dem folgenden Befehl:
|
||||||
|
|
||||||
|
<div class="termy">
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ python -m ensurepip --upgrade
|
||||||
|
|
||||||
|
---> 100%
|
||||||
|
```
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Dieser Befehl installiert `pip`, falls es noch nicht installiert ist, und stellt außerdem sicher, dass die installierte Version von `pip` mindestens so aktuell ist wie die in `ensurepip` verfügbare.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
## `.gitignore` hinzufügen { #add-gitignore }
|
## `.gitignore` hinzufügen { #add-gitignore }
|
||||||
|
|
||||||
Wenn Sie **Git** verwenden (was Sie sollten), fügen Sie eine `.gitignore`-Datei hinzu, um alles in Ihrem `.venv` von Git auszuschließen.
|
Wenn Sie **Git** verwenden (was Sie sollten), fügen Sie eine `.gitignore`-Datei hinzu, um alles in Ihrem `.venv` von Git auszuschließen.
|
||||||
|
|
|
||||||
|
|
@ -255,6 +255,7 @@ Below is a list of English terms and their preferred German translations, separa
|
||||||
* «the default value»: «der Defaultwert»
|
* «the default value»: «der Defaultwert»
|
||||||
* «the default value»: NOT «der Standardwert»
|
* «the default value»: NOT «der Standardwert»
|
||||||
* «the default declaration»: «die Default-Deklaration»
|
* «the default declaration»: «die Default-Deklaration»
|
||||||
|
* «the deployment»: «das Deployment»
|
||||||
* «the dict»: «das Dict»
|
* «the dict»: «das Dict»
|
||||||
* «the dictionary»: «das Dictionary»
|
* «the dictionary»: «das Dictionary»
|
||||||
* «the enumeration»: «die Enumeration»
|
* «the enumeration»: «die Enumeration»
|
||||||
|
|
@ -316,6 +317,7 @@ Below is a list of English terms and their preferred German translations, separa
|
||||||
* «the worker process»: «der Workerprozess»
|
* «the worker process»: «der Workerprozess»
|
||||||
* «the worker process»: NOT «der Arbeiterprozess»
|
* «the worker process»: NOT «der Arbeiterprozess»
|
||||||
* «to commit»: «committen»
|
* «to commit»: «committen»
|
||||||
|
* «to deploy» (in the cloud): «deployen»
|
||||||
* «to modify»: «ändern»
|
* «to modify»: «ändern»
|
||||||
* «to serve» (an application): «bereitstellen»
|
* «to serve» (an application): «bereitstellen»
|
||||||
* «to serve» (a response): «ausliefern»
|
* «to serve» (a response): «ausliefern»
|
||||||
|
|
|
||||||
|
|
@ -443,6 +443,14 @@ The docs UI will interact with the server that you select.
|
||||||
|
|
||||||
///
|
///
|
||||||
|
|
||||||
|
/// note | Technical Details
|
||||||
|
|
||||||
|
The `servers` property in the OpenAPI specification is optional.
|
||||||
|
|
||||||
|
If you don't specify the `servers` parameter and `root_path` is equal to `/`, the `servers` property in the generated OpenAPI schema will be omitted entirely by default, which is the equivalent of a single server with a `url` value of `/`.
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
### Disable automatic server from `root_path` { #disable-automatic-server-from-root-path }
|
### Disable automatic server from `root_path` { #disable-automatic-server-from-root-path }
|
||||||
|
|
||||||
If you don't want **FastAPI** to include an automatic server using the `root_path`, you can use the parameter `root_path_in_servers=False`:
|
If you don't want **FastAPI** to include an automatic server using the `root_path`, you can use the parameter `root_path_in_servers=False`:
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,44 @@ hide:
|
||||||
|
|
||||||
## Latest Changes
|
## Latest Changes
|
||||||
|
|
||||||
|
## 0.123.5
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* ✨ Allow using dependables with `functools.partial()`. PR [#9753](https://github.com/fastapi/fastapi/pull/9753) by [@lieryan](https://github.com/lieryan).
|
||||||
|
* ✨ Add support for wrapped functions (e.g. `@functools.wraps()`) used with forward references. PR [#5077](https://github.com/fastapi/fastapi/pull/5077) by [@lucaswiman](https://github.com/lucaswiman).
|
||||||
|
* ✨ Handle wrapped dependencies. PR [#9555](https://github.com/fastapi/fastapi/pull/9555) by [@phy1729](https://github.com/phy1729).
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* 🐛 Fix optional sequence handling with new union syntax from Python 3.10. PR [#14430](https://github.com/fastapi/fastapi/pull/14430) by [@Viicos](https://github.com/Viicos).
|
||||||
|
|
||||||
|
### Refactors
|
||||||
|
|
||||||
|
* 🔥 Remove dangling extra condiitonal no longer needed. PR [#14435](https://github.com/fastapi/fastapi/pull/14435) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
* ♻️ Refactor internals, update `is_coroutine` check to reuse internal supported variants (unwrap, check class). PR [#14434](https://github.com/fastapi/fastapi/pull/14434) by [@tiangolo](https://github.com/tiangolo).
|
||||||
|
|
||||||
|
### Translations
|
||||||
|
|
||||||
|
* 🌐 Sync German docs. PR [#14367](https://github.com/fastapi/fastapi/pull/14367) by [@nilslindemann](https://github.com/nilslindemann).
|
||||||
|
|
||||||
|
## 0.123.4
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* 🐛 Fix OpenAPI schema support for computed fields when using `separate_input_output_schemas=False`. PR [#13207](https://github.com/fastapi/fastapi/pull/13207) by [@vgrafe](https://github.com/vgrafe).
|
||||||
|
|
||||||
|
### Docs
|
||||||
|
|
||||||
|
* 📝 Fix docstring of `servers` parameter. PR [#14405](https://github.com/fastapi/fastapi/pull/14405) by [@YuriiMotov](https://github.com/YuriiMotov).
|
||||||
|
|
||||||
|
## 0.123.3
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
* 🐛 Fix Query\Header\Cookie parameter model alias. PR [#14360](https://github.com/fastapi/fastapi/pull/14360) by [@YuriiMotov](https://github.com/YuriiMotov).
|
||||||
|
* 🐛 Fix optional sequence handling in `serialize sequence value` with Pydantic V2. PR [#14297](https://github.com/fastapi/fastapi/pull/14297) by [@YuriiMotov](https://github.com/YuriiMotov).
|
||||||
|
|
||||||
## 0.123.2
|
## 0.123.2
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
||||||
|
|
||||||
__version__ = "0.123.2"
|
__version__ = "0.123.5"
|
||||||
|
|
||||||
from starlette import status as status
|
from starlette import status as status
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ from typing import (
|
||||||
|
|
||||||
from fastapi._compat import may_v1, shared
|
from fastapi._compat import may_v1, shared
|
||||||
from fastapi.openapi.constants import REF_TEMPLATE
|
from fastapi.openapi.constants import REF_TEMPLATE
|
||||||
from fastapi.types import IncEx, ModelNameMap
|
from fastapi.types import IncEx, ModelNameMap, UnionType
|
||||||
from pydantic import BaseModel, TypeAdapter, create_model
|
from pydantic import BaseModel, TypeAdapter, create_model
|
||||||
from pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError
|
from pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError
|
||||||
from pydantic import PydanticUndefinedAnnotation as PydanticUndefinedAnnotation
|
from pydantic import PydanticUndefinedAnnotation as PydanticUndefinedAnnotation
|
||||||
|
|
@ -180,8 +180,13 @@ def get_schema_from_model_field(
|
||||||
],
|
],
|
||||||
separate_input_output_schemas: bool = True,
|
separate_input_output_schemas: bool = True,
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
|
computed_fields = field._type_adapter.core_schema.get("schema", {}).get(
|
||||||
|
"computed_fields", []
|
||||||
|
)
|
||||||
override_mode: Union[Literal["validation"], None] = (
|
override_mode: Union[Literal["validation"], None] = (
|
||||||
None if separate_input_output_schemas else "validation"
|
None
|
||||||
|
if (separate_input_output_schemas or len(computed_fields) > 0)
|
||||||
|
else "validation"
|
||||||
)
|
)
|
||||||
# This expects that GenerateJsonSchema was already used to generate the definitions
|
# This expects that GenerateJsonSchema was already used to generate the definitions
|
||||||
json_schema = field_mapping[(field, override_mode or field.mode)]
|
json_schema = field_mapping[(field, override_mode or field.mode)]
|
||||||
|
|
@ -203,9 +208,14 @@ def get_definitions(
|
||||||
Dict[Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue],
|
Dict[Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue],
|
||||||
Dict[str, Dict[str, Any]],
|
Dict[str, Dict[str, Any]],
|
||||||
]:
|
]:
|
||||||
|
has_computed_fields: bool = any(
|
||||||
|
field._type_adapter.core_schema.get("schema", {}).get("computed_fields", [])
|
||||||
|
for field in fields
|
||||||
|
)
|
||||||
|
|
||||||
schema_generator = GenerateJsonSchema(ref_template=REF_TEMPLATE)
|
schema_generator = GenerateJsonSchema(ref_template=REF_TEMPLATE)
|
||||||
override_mode: Union[Literal["validation"], None] = (
|
override_mode: Union[Literal["validation"], None] = (
|
||||||
None if separate_input_output_schemas else "validation"
|
None if (separate_input_output_schemas or has_computed_fields) else "validation"
|
||||||
)
|
)
|
||||||
validation_fields = [field for field in fields if field.mode == "validation"]
|
validation_fields = [field for field in fields if field.mode == "validation"]
|
||||||
serialization_fields = [field for field in fields if field.mode == "serialization"]
|
serialization_fields = [field for field in fields if field.mode == "serialization"]
|
||||||
|
|
@ -371,6 +381,13 @@ def copy_field_info(*, field_info: FieldInfo, annotation: Any) -> FieldInfo:
|
||||||
|
|
||||||
def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]:
|
def serialize_sequence_value(*, field: ModelField, value: Any) -> Sequence[Any]:
|
||||||
origin_type = get_origin(field.field_info.annotation) or field.field_info.annotation
|
origin_type = get_origin(field.field_info.annotation) or field.field_info.annotation
|
||||||
|
if origin_type is Union or origin_type is UnionType: # Handle optional sequences
|
||||||
|
union_args = get_args(field.field_info.annotation)
|
||||||
|
for union_arg in union_args:
|
||||||
|
if union_arg is type(None):
|
||||||
|
continue
|
||||||
|
origin_type = get_origin(union_arg) or union_arg
|
||||||
|
break
|
||||||
assert issubclass(origin_type, shared.sequence_types) # type: ignore[arg-type]
|
assert issubclass(origin_type, shared.sequence_types) # type: ignore[arg-type]
|
||||||
return shared.sequence_annotation_to_type[origin_type](value) # type: ignore[no-any-return]
|
return shared.sequence_annotation_to_type[origin_type](value) # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -301,7 +301,12 @@ class FastAPI(Starlette):
|
||||||
browser tabs open). Or if you want to leave fixed the possible URLs.
|
browser tabs open). Or if you want to leave fixed the possible URLs.
|
||||||
|
|
||||||
If the servers `list` is not provided, or is an empty `list`, the
|
If the servers `list` is not provided, or is an empty `list`, the
|
||||||
default value would be a `dict` with a `url` value of `/`.
|
`servers` property in the generated OpenAPI will be:
|
||||||
|
|
||||||
|
* a `dict` with a `url` value of the application's mounting point
|
||||||
|
(`root_path`) if it's different from `/`.
|
||||||
|
* otherwise, the `servers` property will be omitted from the OpenAPI
|
||||||
|
schema.
|
||||||
|
|
||||||
Each item in the `list` is a `dict` containing:
|
Each item in the `list` is a `dict` containing:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import inspect
|
import inspect
|
||||||
import sys
|
import sys
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from functools import cached_property
|
from functools import cached_property, partial
|
||||||
from typing import Any, Callable, List, Optional, Sequence, Union
|
from typing import Any, Callable, List, Optional, Sequence, Union
|
||||||
|
|
||||||
from fastapi._compat import ModelField
|
from fastapi._compat import ModelField
|
||||||
|
|
@ -75,27 +75,36 @@ class Dependant:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def _unwrapped_call(self) -> Any:
|
||||||
|
if self.call is None:
|
||||||
|
return self.call # pragma: no cover
|
||||||
|
unwrapped = inspect.unwrap(self.call)
|
||||||
|
if isinstance(unwrapped, partial):
|
||||||
|
unwrapped = unwrapped.func
|
||||||
|
return unwrapped
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def is_gen_callable(self) -> bool:
|
def is_gen_callable(self) -> bool:
|
||||||
if inspect.isgeneratorfunction(self.call):
|
if inspect.isgeneratorfunction(self._unwrapped_call):
|
||||||
return True
|
return True
|
||||||
dunder_call = getattr(self.call, "__call__", None) # noqa: B004
|
dunder_call = getattr(self._unwrapped_call, "__call__", None) # noqa: B004
|
||||||
return inspect.isgeneratorfunction(dunder_call)
|
return inspect.isgeneratorfunction(dunder_call)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def is_async_gen_callable(self) -> bool:
|
def is_async_gen_callable(self) -> bool:
|
||||||
if inspect.isasyncgenfunction(self.call):
|
if inspect.isasyncgenfunction(self._unwrapped_call):
|
||||||
return True
|
return True
|
||||||
dunder_call = getattr(self.call, "__call__", None) # noqa: B004
|
dunder_call = getattr(self._unwrapped_call, "__call__", None) # noqa: B004
|
||||||
return inspect.isasyncgenfunction(dunder_call)
|
return inspect.isasyncgenfunction(dunder_call)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def is_coroutine_callable(self) -> bool:
|
def is_coroutine_callable(self) -> bool:
|
||||||
if inspect.isroutine(self.call):
|
if inspect.isroutine(self._unwrapped_call):
|
||||||
return iscoroutinefunction(self.call)
|
return iscoroutinefunction(self._unwrapped_call)
|
||||||
if inspect.isclass(self.call):
|
if inspect.isclass(self._unwrapped_call):
|
||||||
return False
|
return False
|
||||||
dunder_call = getattr(self.call, "__call__", None) # noqa: B004
|
dunder_call = getattr(self._unwrapped_call, "__call__", None) # noqa: B004
|
||||||
return iscoroutinefunction(dunder_call)
|
return iscoroutinefunction(dunder_call)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,8 @@ def get_flat_params(dependant: Dependant) -> List[ModelField]:
|
||||||
|
|
||||||
def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
|
def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
|
||||||
signature = inspect.signature(call)
|
signature = inspect.signature(call)
|
||||||
globalns = getattr(call, "__globals__", {})
|
unwrapped = inspect.unwrap(call)
|
||||||
|
globalns = getattr(unwrapped, "__globals__", {})
|
||||||
typed_params = [
|
typed_params = [
|
||||||
inspect.Parameter(
|
inspect.Parameter(
|
||||||
name=param.name,
|
name=param.name,
|
||||||
|
|
@ -217,12 +218,13 @@ def get_typed_annotation(annotation: Any, globalns: Dict[str, Any]) -> Any:
|
||||||
|
|
||||||
def get_typed_return_annotation(call: Callable[..., Any]) -> Any:
|
def get_typed_return_annotation(call: Callable[..., Any]) -> Any:
|
||||||
signature = inspect.signature(call)
|
signature = inspect.signature(call)
|
||||||
|
unwrapped = inspect.unwrap(call)
|
||||||
annotation = signature.return_annotation
|
annotation = signature.return_annotation
|
||||||
|
|
||||||
if annotation is inspect.Signature.empty:
|
if annotation is inspect.Signature.empty:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
globalns = getattr(call, "__globals__", {})
|
globalns = getattr(unwrapped, "__globals__", {})
|
||||||
return get_typed_annotation(annotation, globalns)
|
return get_typed_annotation(annotation, globalns)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -794,9 +796,8 @@ def request_params_to_args(
|
||||||
)
|
)
|
||||||
value = _get_multidict_value(field, received_params, alias=alias)
|
value = _get_multidict_value(field, received_params, alias=alias)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
params_to_process[field.name] = value
|
params_to_process[field.alias] = value
|
||||||
processed_keys.add(alias or field.alias)
|
processed_keys.add(alias or field.alias)
|
||||||
processed_keys.add(field.name)
|
|
||||||
|
|
||||||
for key in received_params.keys():
|
for key in received_params.keys():
|
||||||
if key not in processed_keys:
|
if key not in processed_keys:
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import email.message
|
||||||
import functools
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
import json
|
import json
|
||||||
import sys
|
|
||||||
from contextlib import AsyncExitStack, asynccontextmanager
|
from contextlib import AsyncExitStack, asynccontextmanager
|
||||||
from enum import Enum, IntEnum
|
from enum import Enum, IntEnum
|
||||||
from typing import (
|
from typing import (
|
||||||
|
|
@ -79,11 +78,6 @@ from starlette.types import AppType, ASGIApp, Lifespan, Receive, Scope, Send
|
||||||
from starlette.websockets import WebSocket
|
from starlette.websockets import WebSocket
|
||||||
from typing_extensions import Annotated, deprecated
|
from typing_extensions import Annotated, deprecated
|
||||||
|
|
||||||
if sys.version_info >= (3, 13): # pragma: no cover
|
|
||||||
from inspect import iscoroutinefunction
|
|
||||||
else: # pragma: no cover
|
|
||||||
from asyncio import iscoroutinefunction
|
|
||||||
|
|
||||||
|
|
||||||
# Copy of starlette.routing.request_response modified to include the
|
# Copy of starlette.routing.request_response modified to include the
|
||||||
# dependencies' AsyncExitStack
|
# dependencies' AsyncExitStack
|
||||||
|
|
@ -308,7 +302,7 @@ def get_request_handler(
|
||||||
embed_body_fields: bool = False,
|
embed_body_fields: bool = False,
|
||||||
) -> Callable[[Request], Coroutine[Any, Any, Response]]:
|
) -> Callable[[Request], Coroutine[Any, Any, Response]]:
|
||||||
assert dependant.call is not None, "dependant.call must be a function"
|
assert dependant.call is not None, "dependant.call must be a function"
|
||||||
is_coroutine = iscoroutinefunction(dependant.call)
|
is_coroutine = dependant.is_coroutine_callable
|
||||||
is_body_form = body_field and isinstance(
|
is_body_form = body_field and isinstance(
|
||||||
body_field.field_info, (params.Form, temp_pydantic_v1_params.Form)
|
body_field.field_info, (params.Form, temp_pydantic_v1_params.Form)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -14,7 +14,7 @@ from fastapi.testclient import TestClient
|
||||||
from pydantic import BaseModel, ConfigDict
|
from pydantic import BaseModel, ConfigDict
|
||||||
from pydantic.fields import FieldInfo
|
from pydantic.fields import FieldInfo
|
||||||
|
|
||||||
from .utils import needs_py_lt_314, needs_pydanticv2
|
from .utils import needs_py310, needs_py_lt_314, needs_pydanticv2
|
||||||
|
|
||||||
|
|
||||||
@needs_pydanticv2
|
@needs_pydanticv2
|
||||||
|
|
@ -136,6 +136,43 @@ def test_is_uploadfile_sequence_annotation():
|
||||||
assert is_uploadfile_sequence_annotation(Union[List[str], List[UploadFile]])
|
assert is_uploadfile_sequence_annotation(Union[List[str], List[UploadFile]])
|
||||||
|
|
||||||
|
|
||||||
|
@needs_pydanticv2
|
||||||
|
def test_serialize_sequence_value_with_optional_list():
|
||||||
|
"""Test that serialize_sequence_value handles optional lists correctly."""
|
||||||
|
from fastapi._compat import v2
|
||||||
|
|
||||||
|
field_info = FieldInfo(annotation=Union[List[str], None])
|
||||||
|
field = v2.ModelField(name="items", field_info=field_info)
|
||||||
|
result = v2.serialize_sequence_value(field=field, value=["a", "b", "c"])
|
||||||
|
assert result == ["a", "b", "c"]
|
||||||
|
assert isinstance(result, list)
|
||||||
|
|
||||||
|
|
||||||
|
@needs_pydanticv2
|
||||||
|
@needs_py310
|
||||||
|
def test_serialize_sequence_value_with_optional_list_pipe_union():
|
||||||
|
"""Test that serialize_sequence_value handles optional lists correctly (with new syntax)."""
|
||||||
|
from fastapi._compat import v2
|
||||||
|
|
||||||
|
field_info = FieldInfo(annotation=list[str] | None)
|
||||||
|
field = v2.ModelField(name="items", field_info=field_info)
|
||||||
|
result = v2.serialize_sequence_value(field=field, value=["a", "b", "c"])
|
||||||
|
assert result == ["a", "b", "c"]
|
||||||
|
assert isinstance(result, list)
|
||||||
|
|
||||||
|
|
||||||
|
@needs_pydanticv2
|
||||||
|
def test_serialize_sequence_value_with_none_first_in_union():
|
||||||
|
"""Test that serialize_sequence_value handles Union[None, List[...]] correctly."""
|
||||||
|
from fastapi._compat import v2
|
||||||
|
|
||||||
|
field_info = FieldInfo(annotation=Union[None, List[str]])
|
||||||
|
field = v2.ModelField(name="items", field_info=field_info)
|
||||||
|
result = v2.serialize_sequence_value(field=field, value=["x", "y"])
|
||||||
|
assert result == ["x", "y"]
|
||||||
|
assert isinstance(result, list)
|
||||||
|
|
||||||
|
|
||||||
@needs_py_lt_314
|
@needs_py_lt_314
|
||||||
def test_is_pv1_scalar_field():
|
def test_is_pv1_scalar_field():
|
||||||
from fastapi._compat import v1
|
from fastapi._compat import v1
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,9 @@ from .utils import needs_pydanticv2
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="client")
|
@pytest.fixture(name="client")
|
||||||
def get_client():
|
def get_client(request):
|
||||||
app = FastAPI()
|
separate_input_output_schemas = request.param
|
||||||
|
app = FastAPI(separate_input_output_schemas=separate_input_output_schemas)
|
||||||
|
|
||||||
from pydantic import BaseModel, computed_field
|
from pydantic import BaseModel, computed_field
|
||||||
|
|
||||||
|
|
@ -32,6 +33,7 @@ def get_client():
|
||||||
return client
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("client", [True, False], indirect=True)
|
||||||
@pytest.mark.parametrize("path", ["/", "/responses"])
|
@pytest.mark.parametrize("path", ["/", "/responses"])
|
||||||
@needs_pydanticv2
|
@needs_pydanticv2
|
||||||
def test_get(client: TestClient, path: str):
|
def test_get(client: TestClient, path: str):
|
||||||
|
|
@ -40,6 +42,7 @@ def test_get(client: TestClient, path: str):
|
||||||
assert response.json() == {"width": 3, "length": 4, "area": 12}
|
assert response.json() == {"width": 3, "length": 4, "area": 12}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("client", [True, False], indirect=True)
|
||||||
@needs_pydanticv2
|
@needs_pydanticv2
|
||||||
def test_openapi_schema(client: TestClient):
|
def test_openapi_schema(client: TestClient):
|
||||||
response = client.get("/openapi.json")
|
response = client.get("/openapi.json")
|
||||||
|
|
|
||||||
|
|
@ -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