Zum Inhalt

Abhängigkeiten mit yield

FastAPI unterstützt Abhängigkeiten, die nach Abschluss einige zusätzliche Schritte ausführen.

Verwenden Sie dazu yield statt return und schreiben Sie die zusätzlichen Schritte / den zusätzlichen Code danach.

Tipp

Stellen Sie sicher, dass Sie yield nur einmal pro Abhängigkeit verwenden.

Technische Details

Jede Funktion, die dekoriert werden kann mit:

kann auch als gültige FastAPI-Abhängigkeit verwendet werden.

Tatsächlich verwendet FastAPI diese beiden Dekoratoren intern.

Eine Datenbank-Abhängigkeit mit yield.

Sie könnten damit beispielsweise eine Datenbanksession erstellen und diese nach Abschluss schließen.

Nur der Code vor und einschließlich der yield-Anweisung wird ausgeführt, bevor eine Response erzeugt wird:

async def get_db():
    db = DBSession()
    try:
        yield db
    finally:
        db.close()

Der geyieldete Wert ist das, was in Pfadoperationen und andere Abhängigkeiten eingefügt wird:

async def get_db():
    db = DBSession()
    try:
        yield db
    finally:
        db.close()

Der auf die yield-Anweisung folgende Code wird ausgeführt, nachdem die Response gesendet wurde:

async def get_db():
    db = DBSession()
    try:
        yield db
    finally:
        db.close()

Tipp

Sie können asynchrone oder reguläre Funktionen verwenden.

FastAPI wird bei jeder das Richtige tun, so wie auch bei normalen Abhängigkeiten.

Eine Abhängigkeit mit yield und try.

Wenn Sie einen try-Block in einer Abhängigkeit mit yield verwenden, empfangen Sie alle Exceptions, die bei Verwendung der Abhängigkeit geworfen wurden.

Wenn beispielsweise ein Code irgendwann in der Mitte, in einer anderen Abhängigkeit oder in einer Pfadoperation, ein „Rollback“ einer Datenbanktransaktion oder einen anderen Fehler verursacht, empfangen Sie die resultierende Exception in Ihrer Abhängigkeit.

Sie können also mit except SomeException diese bestimmte Exception innerhalb der Abhängigkeit handhaben.

Auf die gleiche Weise können Sie finally verwenden, um sicherzustellen, dass die Exit-Schritte ausgeführt werden, unabhängig davon, ob eine Exception geworfen wurde oder nicht.

async def get_db():
    db = DBSession()
    try:
        yield db
    finally:
        db.close()

Unterabhängigkeiten mit yield.

Sie können Unterabhängigkeiten und „Bäume“ von Unterabhängigkeiten beliebiger Größe und Form haben, und einige oder alle davon können yield verwenden.

FastAPI stellt sicher, dass der „Exit-Code“ in jeder Abhängigkeit mit yield in der richtigen Reihenfolge ausgeführt wird.

Beispielsweise kann dependency_c von dependency_b und dependency_b von dependency_a abhängen:

from typing import Annotated

from fastapi import Depends


async def dependency_a():
    dep_a = generate_dep_a()
    try:
        yield dep_a
    finally:
        dep_a.close()


async def dependency_b(dep_a: Annotated[DepA, Depends(dependency_a)]):
    dep_b = generate_dep_b()
    try:
        yield dep_b
    finally:
        dep_b.close(dep_a)


async def dependency_c(dep_b: Annotated[DepB, Depends(dependency_b)]):
    dep_c = generate_dep_c()
    try:
        yield dep_c
    finally:
        dep_c.close(dep_b)
from fastapi import Depends
from typing_extensions import Annotated


async def dependency_a():
    dep_a = generate_dep_a()
    try:
        yield dep_a
    finally:
        dep_a.close()


async def dependency_b(dep_a: Annotated[DepA, Depends(dependency_a)]):
    dep_b = generate_dep_b()
    try:
        yield dep_b
    finally:
        dep_b.close(dep_a)


async def dependency_c(dep_b: Annotated[DepB, Depends(dependency_b)]):
    dep_c = generate_dep_c()
    try:
        yield dep_c
    finally:
        dep_c.close(dep_b)

Tipp

Bevorzugen Sie die Annotated-Version, falls möglich.

from fastapi import Depends


async def dependency_a():
    dep_a = generate_dep_a()
    try:
        yield dep_a
    finally:
        dep_a.close()


async def dependency_b(dep_a=Depends(dependency_a)):
    dep_b = generate_dep_b()
    try:
        yield dep_b
    finally:
        dep_b.close(dep_a)


async def dependency_c(dep_b=Depends(dependency_b)):
    dep_c = generate_dep_c()
    try:
        yield dep_c
    finally:
        dep_c.close(dep_b)

Und alle können yield verwenden.

In diesem Fall benötigt dependency_c zum Ausführen seines Exit-Codes, dass der Wert von dependency_b (hier dep_b genannt) verfügbar ist.

Und wiederum benötigt dependency_b den Wert von dependency_a (hier dep_a genannt) für seinen Exit-Code.

from typing import Annotated

from fastapi import Depends


async def dependency_a():
    dep_a = generate_dep_a()
    try:
        yield dep_a
    finally:
        dep_a.close()


async def dependency_b(dep_a: Annotated[DepA, Depends(dependency_a)]):
    dep_b = generate_dep_b()
    try:
        yield dep_b
    finally:
        dep_b.close(dep_a)


async def dependency_c(dep_b: Annotated[DepB, Depends(dependency_b)]):
    dep_c = generate_dep_c()
    try:
        yield dep_c
    finally:
        dep_c.close(dep_b)
from fastapi import Depends
from typing_extensions import Annotated


async def dependency_a():
    dep_a = generate_dep_a()
    try:
        yield dep_a
    finally:
        dep_a.close()


async def dependency_b(dep_a: Annotated[DepA, Depends(dependency_a)]):
    dep_b = generate_dep_b()
    try:
        yield dep_b
    finally:
        dep_b.close(dep_a)


async def dependency_c(dep_b: Annotated[DepB, Depends(dependency_b)]):
    dep_c = generate_dep_c()
    try:
        yield dep_c
    finally:
        dep_c.close(dep_b)

Tipp

Bevorzugen Sie die Annotated-Version, falls möglich.

from fastapi import Depends


async def dependency_a():
    dep_a = generate_dep_a()
    try:
        yield dep_a
    finally:
        dep_a.close()


async def dependency_b(dep_a=Depends(dependency_a)):
    dep_b = generate_dep_b()
    try:
        yield dep_b
    finally:
        dep_b.close(dep_a)


async def dependency_c(dep_b=Depends(dependency_b)):
    dep_c = generate_dep_c()
    try:
        yield dep_c
    finally:
        dep_c.close(dep_b)

Auf die gleiche Weise könnten Sie einige Abhängigkeiten mit yield und einige andere Abhängigkeiten mit return haben, und alle können beliebig voneinander abhängen.

Und Sie könnten eine einzelne Abhängigkeit haben, die auf mehreren geyieldeten Abhängigkeiten basiert, usw.

Sie können beliebige Kombinationen von Abhängigkeiten haben.

FastAPI stellt sicher, dass alles in der richtigen Reihenfolge ausgeführt wird.

Technische Details

Dieses funktioniert dank Pythons Kontextmanager.

FastAPI verwendet sie intern, um das zu erreichen.

Abhängigkeiten mit yield und HTTPException.

Sie haben gesehen, dass Ihre Abhängigkeiten yield verwenden können und try-Blöcke haben können, die Exceptions abfangen.

Auf die gleiche Weise könnten Sie im Exit-Code nach dem yield eine HTTPException oder ähnliches auslösen.

Tipp

Dies ist eine etwas fortgeschrittene Technik, die Sie in den meisten Fällen nicht wirklich benötigen, da Sie Exceptions (einschließlich HTTPException) innerhalb des restlichen Anwendungscodes auslösen können, beispielsweise in der Pfadoperation-Funktion.

Aber es ist für Sie da, wenn Sie es brauchen. 🤓

from typing import Annotated

from fastapi import Depends, FastAPI, HTTPException

app = FastAPI()


data = {
    "plumbus": {"description": "Freshly pickled plumbus", "owner": "Morty"},
    "portal-gun": {"description": "Gun to create portals", "owner": "Rick"},
}


class OwnerError(Exception):
    pass


def get_username():
    try:
        yield "Rick"
    except OwnerError as e:
        raise HTTPException(status_code=400, detail=f"Owner error: {e}")


@app.get("/items/{item_id}")
def get_item(item_id: str, username: Annotated[str, Depends(get_username)]):
    if item_id not in data:
        raise HTTPException(status_code=404, detail="Item not found")
    item = data[item_id]
    if item["owner"] != username:
        raise OwnerError(username)
    return item
from fastapi import Depends, FastAPI, HTTPException
from typing_extensions import Annotated

app = FastAPI()


data = {
    "plumbus": {"description": "Freshly pickled plumbus", "owner": "Morty"},
    "portal-gun": {"description": "Gun to create portals", "owner": "Rick"},
}


class OwnerError(Exception):
    pass


def get_username():
    try:
        yield "Rick"
    except OwnerError as e:
        raise HTTPException(status_code=400, detail=f"Owner error: {e}")


@app.get("/items/{item_id}")
def get_item(item_id: str, username: Annotated[str, Depends(get_username)]):
    if item_id not in data:
        raise HTTPException(status_code=404, detail="Item not found")
    item = data[item_id]
    if item["owner"] != username:
        raise OwnerError(username)
    return item

Tipp

Bevorzugen Sie die Annotated-Version, falls möglich.

from fastapi import Depends, FastAPI, HTTPException

app = FastAPI()


data = {
    "plumbus": {"description": "Freshly pickled plumbus", "owner": "Morty"},
    "portal-gun": {"description": "Gun to create portals", "owner": "Rick"},
}


class OwnerError(Exception):
    pass


def get_username():
    try:
        yield "Rick"
    except OwnerError as e:
        raise HTTPException(status_code=400, detail=f"Owner error: {e}")


@app.get("/items/{item_id}")
def get_item(item_id: str, username: str = Depends(get_username)):
    if item_id not in data:
        raise HTTPException(status_code=404, detail="Item not found")
    item = data[item_id]
    if item["owner"] != username:
        raise OwnerError(username)
    return item

Eine Alternative zum Abfangen von Exceptions (und möglicherweise auch zum Auslösen einer weiteren HTTPException) besteht darin, einen benutzerdefinierten Exceptionhandler zu erstellen.

Ausführung von Abhängigkeiten mit yield

Die Ausführungsreihenfolge ähnelt mehr oder weniger dem folgenden Diagramm. Die Zeit verläuft von oben nach unten. Und jede Spalte ist einer der interagierenden oder Code-ausführenden Teilnehmer.

sequenceDiagram

participant client as Client
participant handler as Exceptionhandler
participant dep as Abhängigkeit mit yield
participant operation as Pfadoperation
participant tasks as Hintergrundtasks

    Note over client,operation: Kann Exceptions auslösen, inklusive HTTPException
    client ->> dep: Startet den Request
    Note over dep: Führt den Code bis zum yield aus
    opt Löst Exception aus
        dep -->> handler: Löst Exception aus
        handler -->> client: HTTP-Error-Response
    end
    dep ->> operation: Führt Abhängigkeit aus, z. B. DB-Session
    opt Löst aus
        operation -->> dep: Löst Exception aus (z. B. HTTPException)
        opt Handhabt
            dep -->> dep: Kann Exception abfangen, eine neue HTTPException auslösen, andere Exceptions auslösen
            dep -->> handler: Leitet Exception automatisch weiter
        end
        handler -->> client: HTTP-Error-Response
    end
    operation ->> client: Sendet Response an Client
    Note over client,operation: Response wurde gesendet, kann nicht mehr geändert werden
    opt Tasks
        operation -->> tasks: Sendet Hintergrundtasks
    end
    opt Löst andere Exception aus
        tasks -->> tasks: Handhabt Exception im Hintergrundtask-Code
    end

Info

Es wird nur eine Response an den Client gesendet. Es kann eine Error-Response oder die Response der Pfadoperation sein.

Nachdem eine dieser Responses gesendet wurde, kann keine weitere Response gesendet werden.

Tipp

Obiges Diagramm verwendet HTTPException, aber Sie können auch jede andere Exception auslösen, die Sie in einer Abhängigkeit mit yield abfangen, oder mit einem benutzerdefinierten Exceptionhandler erstellt haben.

Wenn Sie eine Exception auslösen, wird diese mit yield an die Abhängigkeiten übergeben, einschließlich HTTPException, und dann erneut an die Exceptionhandler. Wenn es für diese Exception keinen Exceptionhandler gibt, wird sie von der internen Default-ServerErrorMiddleware gehandhabt, was einen HTTP-Statuscode 500 zurückgibt, um den Client darüber zu informieren, dass ein Fehler auf dem Server aufgetreten ist.

Abhängigkeiten mit yield, HTTPException und Hintergrundtasks

Achtung

Sie benötigen diese technischen Details höchstwahrscheinlich nicht, Sie können diesen Abschnitt überspringen und weiter unten fortfahren.

Diese Details sind vor allem dann nützlich, wenn Sie eine Version von FastAPI vor 0.106.0 verwendet haben und Ressourcen aus Abhängigkeiten mit yield in Hintergrundtasks verwendet haben.

Vor FastAPI 0.106.0 war das Auslösen von Exceptions nach yield nicht möglich, der Exit-Code in Abhängigkeiten mit yield wurde ausgeführt, nachdem die Response gesendet wurde, die Exceptionhandler wären also bereits ausgeführt worden.

Dies wurde hauptsächlich so konzipiert, damit die gleichen Objekte, die durch Abhängigkeiten geyieldet werden, innerhalb von Hintergrundtasks verwendet werden können, da der Exit-Code ausgeführt wird, nachdem die Hintergrundtasks abgeschlossen sind.

Da dies jedoch bedeuten würde, darauf zu warten, dass die Response durch das Netzwerk reist, während eine Ressource unnötigerweise in einer Abhängigkeit mit yield gehalten wird (z. B. eine Datenbankverbindung), wurde dies in FastAPI 0.106.0 geändert.

Tipp

Darüber hinaus handelt es sich bei einem Hintergrundtask normalerweise um einen unabhängigen Satz von Logik, der separat behandelt werden sollte, mit eigenen Ressourcen (z. B. einer eigenen Datenbankverbindung).

Auf diese Weise erhalten Sie wahrscheinlich saubereren Code.

Wenn Sie sich früher auf dieses Verhalten verlassen haben, sollten Sie jetzt die Ressourcen für Hintergrundtasks innerhalb des Hintergrundtasks selbst erstellen und intern nur Daten verwenden, die nicht von den Ressourcen von Abhängigkeiten mit yield abhängen.

Anstatt beispielsweise dieselbe Datenbanksitzung zu verwenden, würden Sie eine neue Datenbanksitzung innerhalb des Hintergrundtasks erstellen und die Objekte mithilfe dieser neuen Sitzung aus der Datenbank abrufen. Und anstatt das Objekt aus der Datenbank als Parameter an die Hintergrundtask-Funktion zu übergeben, würden Sie die ID dieses Objekts übergeben und das Objekt dann innerhalb der Hintergrundtask-Funktion erneut laden.

Kontextmanager

Was sind „Kontextmanager“

„Kontextmanager“ (Englisch „Context Manager“) sind bestimmte Python-Objekte, die Sie in einer with-Anweisung verwenden können.

Beispielsweise können Sie with verwenden, um eine Datei auszulesen:

with open("./somefile.txt") as f:
    contents = f.read()
    print(contents)

Im Hintergrund erstellt das open("./somefile.txt") ein Objekt, das als „Kontextmanager“ bezeichnet wird.

Dieser stellt sicher dass, wenn der with-Block beendet ist, die Datei geschlossen wird, auch wenn Exceptions geworfen wurden.

Wenn Sie eine Abhängigkeit mit yield erstellen, erstellt FastAPI dafür intern einen Kontextmanager und kombiniert ihn mit einigen anderen zugehörigen Tools.

Kontextmanager in Abhängigkeiten mit yield verwenden

Achtung

Dies ist mehr oder weniger eine „fortgeschrittene“ Idee.

Wenn Sie gerade erst mit FastAPI beginnen, möchten Sie das vielleicht vorerst überspringen.

In Python können Sie Kontextmanager erstellen, indem Sie eine Klasse mit zwei Methoden erzeugen: __enter__() und __exit__().

Sie können solche auch innerhalb von FastAPI-Abhängigkeiten mit yield verwenden, indem Sie with- oder async with-Anweisungen innerhalb der Abhängigkeits-Funktion verwenden:

class MySuperContextManager:
    def __init__(self):
        self.db = DBSession()

    def __enter__(self):
        return self.db

    def __exit__(self, exc_type, exc_value, traceback):
        self.db.close()


async def get_db():
    with MySuperContextManager() as db:
        yield db

Tipp

Andere Möglichkeiten, einen Kontextmanager zu erstellen, sind:

Verwenden Sie diese, um eine Funktion zu dekorieren, die ein einziges yield hat.

Das ist es auch, was FastAPI intern für Abhängigkeiten mit yield verwendet.

Aber Sie müssen die Dekoratoren nicht für FastAPI-Abhängigkeiten verwenden (und das sollten Sie auch nicht).

FastAPI erledigt das intern für Sie.