Abhängigkeiten¶
FastAPI hat ein sehr mächtiges, aber intuitives Dependency Injection System.
Es ist so konzipiert, sehr einfach zu verwenden zu sein und es jedem Entwickler sehr leicht zu machen, andere Komponenten mit FastAPI zu integrieren.
Was ist „Dependency Injection“¶
„Dependency Injection“ bedeutet in der Programmierung, dass es für Ihren Code (in diesem Fall Ihre Pfadoperation-Funktionen) eine Möglichkeit gibt, Dinge zu deklarieren, die er verwenden möchte und die er zum Funktionieren benötigt: „Abhängigkeiten“ – „Dependencies“.
Das System (in diesem Fall FastAPI) kümmert sich dann darum, Ihren Code mit den erforderlichen Abhängigkeiten zu versorgen („die Abhängigkeiten einfügen“ – „inject the dependencies“).
Das ist sehr nützlich, wenn Sie:
- Eine gemeinsame Logik haben (die gleiche Code-Logik immer und immer wieder).
- Datenbankverbindungen teilen.
- Sicherheit, Authentifizierung, Rollenanforderungen, usw. durchsetzen.
- Und viele andere Dinge ...
All dies, während Sie Codeverdoppelung minimieren.
Erste Schritte¶
Sehen wir uns ein sehr einfaches Beispiel an. Es ist so einfach, dass es vorerst nicht sehr nützlich ist.
Aber so können wir uns besser auf die Funktionsweise des Dependency Injection Systems konzentrieren.
Erstellen Sie eine Abhängigkeit („Dependable“)¶
Konzentrieren wir uns zunächst auf die Abhängigkeit - die Dependency.
Es handelt sich einfach um eine Funktion, die die gleichen Parameter entgegennimmt wie eine Pfadoperation-Funktion:
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons
@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
return commons
from typing import Annotated, Union
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(
q: Union[str, None] = None, skip: int = 0, limit: int = 100
):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons
@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
return commons
from typing import Union
from fastapi import Depends, FastAPI
from typing_extensions import Annotated
app = FastAPI()
async def common_parameters(
q: Union[str, None] = None, skip: int = 0, limit: int = 100
):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons
@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
return commons
Tipp
Bevorzugen Sie die Annotated
-Version, falls möglich.
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
return commons
Tipp
Bevorzugen Sie die Annotated
-Version, falls möglich.
from typing import Union
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(
q: Union[str, None] = None, skip: int = 0, limit: int = 100
):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
return commons
Das war's schon.
Zwei Zeilen.
Und sie hat die gleiche Form und Struktur wie alle Ihre Pfadoperation-Funktionen.
Sie können sie sich als Pfadoperation-Funktion ohne den „Dekorator“ (ohne @app.get("/some-path")
) vorstellen.
Und sie kann alles zurückgeben, was Sie möchten.
In diesem Fall erwartet diese Abhängigkeit:
- Einen optionalen Query-Parameter
q
, der einstr
ist. - Einen optionalen Query-Parameter
skip
, der einint
ist und standardmäßig0
ist. - Einen optionalen Query-Parameter
limit
, der einint
ist und standardmäßig100
ist.
Und dann wird einfach ein dict
zurückgegeben, welches diese Werte enthält.
Info
FastAPI unterstützt (und empfiehlt die Verwendung von) Annotated
seit Version 0.95.0.
Wenn Sie eine ältere Version haben, werden Sie Fehler angezeigt bekommen, wenn Sie versuchen, Annotated
zu verwenden.
Bitte aktualisieren Sie FastAPI daher mindestens zu Version 0.95.1, bevor Sie Annotated
verwenden.
Depends
importieren¶
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons
@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
return commons
from typing import Annotated, Union
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(
q: Union[str, None] = None, skip: int = 0, limit: int = 100
):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons
@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
return commons
from typing import Union
from fastapi import Depends, FastAPI
from typing_extensions import Annotated
app = FastAPI()
async def common_parameters(
q: Union[str, None] = None, skip: int = 0, limit: int = 100
):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons
@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
return commons
Tipp
Bevorzugen Sie die Annotated
-Version, falls möglich.
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
return commons
Tipp
Bevorzugen Sie die Annotated
-Version, falls möglich.
from typing import Union
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(
q: Union[str, None] = None, skip: int = 0, limit: int = 100
):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
return commons
Deklarieren der Abhängigkeit im „Dependant“¶
So wie auch Body
, Query
, usw., verwenden Sie Depends
mit den Parametern Ihrer Pfadoperation-Funktion:
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons
@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
return commons
from typing import Annotated, Union
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(
q: Union[str, None] = None, skip: int = 0, limit: int = 100
):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons
@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
return commons
from typing import Union
from fastapi import Depends, FastAPI
from typing_extensions import Annotated
app = FastAPI()
async def common_parameters(
q: Union[str, None] = None, skip: int = 0, limit: int = 100
):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons
@app.get("/users/")
async def read_users(commons: Annotated[dict, Depends(common_parameters)]):
return commons
Tipp
Bevorzugen Sie die Annotated
-Version, falls möglich.
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
return commons
Tipp
Bevorzugen Sie die Annotated
-Version, falls möglich.
from typing import Union
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(
q: Union[str, None] = None, skip: int = 0, limit: int = 100
):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
return commons
Obwohl Sie Depends
in den Parametern Ihrer Funktion genauso verwenden wie Body
, Query
, usw., funktioniert Depends
etwas anders.
Sie übergeben Depends
nur einen einzigen Parameter.
Dieser Parameter muss so etwas wie eine Funktion sein.
Sie rufen diese nicht direkt auf (fügen Sie am Ende keine Klammern hinzu), sondern übergeben sie einfach als Parameter an Depends()
.
Und diese Funktion akzeptiert Parameter auf die gleiche Weise wie Pfadoperation-Funktionen.
Tipp
Im nächsten Kapitel erfahren Sie, welche anderen „Dinge“, außer Funktionen, Sie als Abhängigkeiten verwenden können.
Immer wenn ein neuer Request eintrifft, kümmert sich FastAPI darum:
- Ihre Abhängigkeitsfunktion („Dependable“) mit den richtigen Parametern aufzurufen.
- Sich das Ergebnis von dieser Funktion zu holen.
- Dieses Ergebnis dem Parameter Ihrer Pfadoperation-Funktion zuzuweisen.
graph TB
common_parameters(["common_parameters"])
read_items["/items/"]
read_users["/users/"]
common_parameters --> read_items
common_parameters --> read_users
Auf diese Weise schreiben Sie gemeinsam genutzten Code nur einmal, und FastAPI kümmert sich darum, ihn für Ihre Pfadoperationen aufzurufen.
Check
Beachten Sie, dass Sie keine spezielle Klasse erstellen und diese irgendwo an FastAPI übergeben müssen, um sie zu „registrieren“ oder so ähnlich.
Sie übergeben es einfach an Depends
und FastAPI weiß, wie der Rest erledigt wird.
Annotated
-Abhängigkeiten wiederverwenden¶
In den Beispielen oben sehen Sie, dass es ein kleines bisschen Codeverdoppelung gibt.
Wenn Sie die Abhängigkeit common_parameters()
verwenden, müssen Sie den gesamten Parameter mit der Typannotation und Depends()
schreiben:
commons: Annotated[dict, Depends(common_parameters)]
Da wir jedoch Annotated
verwenden, können wir diesen Annotated
-Wert in einer Variablen speichern und an mehreren Stellen verwenden:
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
CommonsDep = Annotated[dict, Depends(common_parameters)]
@app.get("/items/")
async def read_items(commons: CommonsDep):
return commons
@app.get("/users/")
async def read_users(commons: CommonsDep):
return commons
from typing import Annotated, Union
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(
q: Union[str, None] = None, skip: int = 0, limit: int = 100
):
return {"q": q, "skip": skip, "limit": limit}
CommonsDep = Annotated[dict, Depends(common_parameters)]
@app.get("/items/")
async def read_items(commons: CommonsDep):
return commons
@app.get("/users/")
async def read_users(commons: CommonsDep):
return commons
from typing import Union
from fastapi import Depends, FastAPI
from typing_extensions import Annotated
app = FastAPI()
async def common_parameters(
q: Union[str, None] = None, skip: int = 0, limit: int = 100
):
return {"q": q, "skip": skip, "limit": limit}
CommonsDep = Annotated[dict, Depends(common_parameters)]
@app.get("/items/")
async def read_items(commons: CommonsDep):
return commons
@app.get("/users/")
async def read_users(commons: CommonsDep):
return commons
Tipp
Das ist schlicht Standard-Python, es wird als „Typalias“ bezeichnet und ist eigentlich nicht FastAPI-spezifisch.
Da FastAPI jedoch auf Standard-Python, einschließlich Annotated
, basiert, können Sie diesen Trick in Ihrem Code verwenden. 😎
Die Abhängigkeiten funktionieren weiterhin wie erwartet, und das Beste daran ist, dass die Typinformationen erhalten bleiben, was bedeutet, dass Ihr Editor Ihnen weiterhin automatische Vervollständigung, Inline-Fehler, usw. bieten kann. Das Gleiche gilt für andere Tools wie mypy
.
Das ist besonders nützlich, wenn Sie es in einer großen Codebasis verwenden, in der Sie in vielen Pfadoperationen immer wieder dieselben Abhängigkeiten verwenden.
async
oder nicht async
¶
Da Abhängigkeiten auch von FastAPI aufgerufen werden (so wie Ihre Pfadoperation-Funktionen), gelten beim Definieren Ihrer Funktionen die gleichen Regeln.
Sie können async def
oder einfach def
verwenden.
Und Sie können Abhängigkeiten mit async def
innerhalb normaler def
-Pfadoperation-Funktionen oder def
-Abhängigkeiten innerhalb von async def
-Pfadoperation-Funktionen, usw. deklarieren.
Es spielt keine Rolle. FastAPI weiß, was zu tun ist.
Hinweis
Wenn Ihnen das nichts sagt, lesen Sie den Async: „In Eile?“-Abschnitt über async
und await
in der Dokumentation.
Integriert in OpenAPI¶
Alle Requestdeklarationen, -validierungen und -anforderungen Ihrer Abhängigkeiten (und Unterabhängigkeiten) werden in dasselbe OpenAPI-Schema integriert.
Die interaktive Dokumentation enthält also auch alle Informationen aus diesen Abhängigkeiten:
Einfache Verwendung¶
Näher betrachtet, werden Pfadoperation-Funktionen deklariert, um verwendet zu werden, wann immer ein Pfad und eine Operation übereinstimmen, und dann kümmert sich FastAPI darum, die Funktion mit den richtigen Parametern aufzurufen, die Daten aus der Anfrage extrahierend.
Tatsächlich funktionieren alle (oder die meisten) Webframeworks auf die gleiche Weise.
Sie rufen diese Funktionen niemals direkt auf. Sie werden von Ihrem Framework aufgerufen (in diesem Fall FastAPI).
Mit dem Dependency Injection System können Sie FastAPI ebenfalls mitteilen, dass Ihre Pfadoperation-Funktion von etwas anderem „abhängt“, das vor Ihrer Pfadoperation-Funktion ausgeführt werden soll, und FastAPI kümmert sich darum, es auszuführen und die Ergebnisse zu „injizieren“.
Andere gebräuchliche Begriffe für dieselbe Idee der „Abhängigkeitsinjektion“ sind:
- Ressourcen
- Provider
- Services
- Injectables
- Komponenten
FastAPI-Plugins¶
Integrationen und „Plugins“ können mit dem Dependency Injection System erstellt werden. Aber tatsächlich besteht keine Notwendigkeit, „Plugins“ zu erstellen, da es durch die Verwendung von Abhängigkeiten möglich ist, eine unendliche Anzahl von Integrationen und Interaktionen zu deklarieren, die dann für Ihre Pfadoperation-Funktionen verfügbar sind.
Und Abhängigkeiten können auf sehr einfache und intuitive Weise erstellt werden, sodass Sie einfach die benötigten Python-Packages importieren und sie in wenigen Codezeilen, im wahrsten Sinne des Wortes, mit Ihren API-Funktionen integrieren.
Beispiele hierfür finden Sie in den nächsten Kapiteln zu relationalen und NoSQL-Datenbanken, Sicherheit usw.
FastAPI-Kompatibilität¶
Die Einfachheit des Dependency Injection Systems macht FastAPI kompatibel mit:
- allen relationalen Datenbanken
- NoSQL-Datenbanken
- externen Packages
- externen APIs
- Authentifizierungs- und Autorisierungssystemen
- API-Nutzungs-Überwachungssystemen
- Responsedaten-Injektionssystemen
- usw.
Einfach und leistungsstark¶
Obwohl das hierarchische Dependency Injection System sehr einfach zu definieren und zu verwenden ist, ist es dennoch sehr mächtig.
Sie können Abhängigkeiten definieren, die selbst wiederum Abhängigkeiten definieren können.
Am Ende wird ein hierarchischer Baum von Abhängigkeiten erstellt, und das Dependency Injection System kümmert sich darum, alle diese Abhängigkeiten (und deren Unterabhängigkeiten) für Sie aufzulösen und die Ergebnisse bei jedem Schritt einzubinden (zu injizieren).
Nehmen wir zum Beispiel an, Sie haben vier API-Endpunkte (Pfadoperationen):
/items/public/
/items/private/
/users/{user_id}/activate
/items/pro/
Dann könnten Sie für jeden davon unterschiedliche Berechtigungsanforderungen hinzufügen, nur mit Abhängigkeiten und Unterabhängigkeiten:
graph TB
current_user(["current_user"])
active_user(["active_user"])
admin_user(["admin_user"])
paying_user(["paying_user"])
public["/items/public/"]
private["/items/private/"]
activate_user["/users/{user_id}/activate"]
pro_items["/items/pro/"]
current_user --> active_user
active_user --> admin_user
active_user --> paying_user
current_user --> public
active_user --> private
admin_user --> activate_user
paying_user --> pro_items
Integriert mit OpenAPI¶
Alle diese Abhängigkeiten, während sie ihre Anforderungen deklarieren, fügen auch Parameter, Validierungen, usw. zu Ihren Pfadoperationen hinzu.
FastAPI kümmert sich darum, alles zum OpenAPI-Schema hinzuzufügen, damit es in den interaktiven Dokumentationssystemen angezeigt wird.