Lifespan-Events¶
Sie können Logik (Code) definieren, die ausgeführt werden soll, bevor die Anwendung hochfährt. Dies bedeutet, dass dieser Code einmal ausgeführt wird, bevor die Anwendung beginnt, Requests entgegenzunehmen.
Auf die gleiche Weise können Sie Logik (Code) definieren, die ausgeführt werden soll, wenn die Anwendung heruntergefahren wird. In diesem Fall wird dieser Code einmal ausgeführt, nachdem möglicherweise viele Requests bearbeitet wurden.
Da dieser Code ausgeführt wird, bevor die Anwendung beginnt, Requests entgegenzunehmen, und unmittelbar, nachdem sie die Bearbeitung von Requests abgeschlossen hat, deckt er die gesamte Lebensdauer – „Lifespan“ – der Anwendung ab (das Wort „Lifespan“ wird gleich wichtig sein 😉).
Dies kann sehr nützlich sein, um Ressourcen einzurichten, die Sie in der gesamten Anwendung verwenden wollen und die von Requests gemeinsam genutzt werden und/oder die Sie anschließend aufräumen müssen. Zum Beispiel ein Pool von Datenbankverbindungen oder das Laden eines gemeinsam genutzten Modells für maschinelles Lernen.
Anwendungsfall¶
Beginnen wir mit einem Beispiel-Anwendungsfall und schauen uns dann an, wie wir ihn mit dieser Methode implementieren können.
Stellen wir uns vor, Sie verfügen über einige Modelle für maschinelles Lernen, die Sie zur Bearbeitung von Requests verwenden möchten. 🤖
Die gleichen Modelle werden von den Requests gemeinsam genutzt, es handelt sich also nicht um ein Modell pro Request, pro Benutzer, oder ähnliches.
Stellen wir uns vor, dass das Laden des Modells eine ganze Weile dauern kann, da viele Daten von der Festplatte gelesen werden müssen. Sie möchten das also nicht für jeden Request tun.
Sie könnten das auf der obersten Ebene des Moduls/der Datei machen, aber das würde auch bedeuten, dass das Modell geladen wird, selbst wenn Sie nur einen einfachen automatisierten Test ausführen, dann wäre dieser Test langsam, weil er warten müsste, bis das Modell geladen ist, bevor er einen davon unabhängigen Teil des Codes ausführen könnte.
Das wollen wir besser machen: Laden wir das Modell, bevor die Requests bearbeitet werden, aber unmittelbar bevor die Anwendung beginnt, Requests zu empfangen, und nicht, während der Code geladen wird.
Lifespan¶
Sie können diese Logik beim Hochfahren und Herunterfahren mithilfe des lifespan
-Parameters der FastAPI
-App und eines „Kontextmanagers“ definieren (ich zeige Ihnen gleich, was das ist).
Beginnen wir mit einem Beispiel und sehen es uns dann im Detail an.
Wir erstellen eine asynchrone Funktion lifespan()
mit yield
wie folgt:
from contextlib import asynccontextmanager
from fastapi import FastAPI
def fake_answer_to_everything_ml_model(x: float):
return x * 42
ml_models = {}
@asynccontextmanager
async def lifespan(app: FastAPI):
# Load the ML model
ml_models["answer_to_everything"] = fake_answer_to_everything_ml_model
yield
# Clean up the ML models and release the resources
ml_models.clear()
app = FastAPI(lifespan=lifespan)
@app.get("/predict")
async def predict(x: float):
result = ml_models["answer_to_everything"](x)
return {"result": result}
Hier simulieren wir das langsame Hochfahren, das Laden des Modells, indem wir die (Fake-)Modellfunktion vor dem yield
in das Dictionary mit Modellen für maschinelles Lernen einfügen. Dieser Code wird ausgeführt, bevor die Anwendung beginnt, Requests entgegenzunehmen, während des Hochfahrens.
Und dann, direkt nach dem yield
, entladen wir das Modell. Dieser Code wird unmittelbar vor dem Herunterfahren ausgeführt, nachdem die Anwendung die Bearbeitung von Requests abgeschlossen hat. Dadurch könnten beispielsweise Ressourcen wie Arbeitsspeicher oder eine GPU freigegeben werden.
Tipp
Das Herunterfahren würde erfolgen, wenn Sie die Anwendung stoppen.
Möglicherweise müssen Sie eine neue Version starten, oder Sie haben es einfach satt, sie auszuführen. 🤷
Lifespan-Funktion¶
Das Erste, was auffällt, ist, dass wir eine asynchrone Funktion mit yield
definieren. Das ist sehr ähnlich zu Abhängigkeiten mit yield
.
from contextlib import asynccontextmanager
from fastapi import FastAPI
def fake_answer_to_everything_ml_model(x: float):
return x * 42
ml_models = {}
@asynccontextmanager
async def lifespan(app: FastAPI):
# Load the ML model
ml_models["answer_to_everything"] = fake_answer_to_everything_ml_model
yield
# Clean up the ML models and release the resources
ml_models.clear()
app = FastAPI(lifespan=lifespan)
@app.get("/predict")
async def predict(x: float):
result = ml_models["answer_to_everything"](x)
return {"result": result}
Der erste Teil der Funktion, vor dem yield
, wird ausgeführt bevor die Anwendung startet.
Und der Teil nach yield
wird ausgeführt, nachdem die Anwendung beendet ist.
Asynchroner Kontextmanager¶
Wie Sie sehen, ist die Funktion mit einem @asynccontextmanager
versehen.
Dadurch wird die Funktion in einen sogenannten „asynchronen Kontextmanager“ umgewandelt.
from contextlib import asynccontextmanager
from fastapi import FastAPI
def fake_answer_to_everything_ml_model(x: float):
return x * 42
ml_models = {}
@asynccontextmanager
async def lifespan(app: FastAPI):
# Load the ML model
ml_models["answer_to_everything"] = fake_answer_to_everything_ml_model
yield
# Clean up the ML models and release the resources
ml_models.clear()
app = FastAPI(lifespan=lifespan)
@app.get("/predict")
async def predict(x: float):
result = ml_models["answer_to_everything"](x)
return {"result": result}
Ein Kontextmanager in Python ist etwas, das Sie in einer with
-Anweisung verwenden können, zum Beispiel kann open()
als Kontextmanager verwendet werden:
with open("file.txt") as file:
file.read()
In neueren Versionen von Python gibt es auch einen asynchronen Kontextmanager. Sie würden ihn mit async with
verwenden:
async with lifespan(app):
await do_stuff()
Wenn Sie wie oben einen Kontextmanager oder einen asynchronen Kontextmanager erstellen, führt dieser vor dem Betreten des with
-Blocks den Code vor dem yield
aus, und nach dem Verlassen des with
-Blocks wird er den Code nach dem yield
ausführen.
In unserem obigen Codebeispiel verwenden wir ihn nicht direkt, sondern übergeben ihn an FastAPI, damit es ihn verwenden kann.
Der Parameter lifespan
der FastAPI
-App benötigt einen asynchronen Kontextmanager, wir können ihm also unseren neuen asynchronen Kontextmanager lifespan
übergeben.
from contextlib import asynccontextmanager
from fastapi import FastAPI
def fake_answer_to_everything_ml_model(x: float):
return x * 42
ml_models = {}
@asynccontextmanager
async def lifespan(app: FastAPI):
# Load the ML model
ml_models["answer_to_everything"] = fake_answer_to_everything_ml_model
yield
# Clean up the ML models and release the resources
ml_models.clear()
app = FastAPI(lifespan=lifespan)
@app.get("/predict")
async def predict(x: float):
result = ml_models["answer_to_everything"](x)
return {"result": result}
Alternative Events (deprecated)¶
Achtung
Der empfohlene Weg, das Hochfahren und Herunterfahren zu handhaben, ist die Verwendung des lifespan
-Parameters der FastAPI
-App, wie oben beschrieben. Wenn Sie einen lifespan
-Parameter übergeben, werden die startup
- und shutdown
-Eventhandler nicht mehr aufgerufen. Es ist entweder alles lifespan
oder alles Events, nicht beides.
Sie können diesen Teil wahrscheinlich überspringen.
Es gibt eine alternative Möglichkeit, diese Logik zu definieren, sodass sie beim Hochfahren und beim Herunterfahren ausgeführt wird.
Sie können Eventhandler (Funktionen) definieren, die ausgeführt werden sollen, bevor die Anwendung hochgefahren wird oder wenn die Anwendung heruntergefahren wird.
Diese Funktionen können mit async def
oder normalem def
deklariert werden.
startup
-Event¶
Um eine Funktion hinzuzufügen, die vor dem Start der Anwendung ausgeführt werden soll, deklarieren Sie diese mit dem Event startup
:
from fastapi import FastAPI
app = FastAPI()
items = {}
@app.on_event("startup")
async def startup_event():
items["foo"] = {"name": "Fighters"}
items["bar"] = {"name": "Tenders"}
@app.get("/items/{item_id}")
async def read_items(item_id: str):
return items[item_id]
In diesem Fall initialisiert die Eventhandler-Funktion startup
die „Datenbank“ der Items (nur ein dict
) mit einigen Werten.
Sie können mehr als eine Eventhandler-Funktion hinzufügen.
Und Ihre Anwendung empfängt erst dann Anfragen, wenn alle startup
-Eventhandler abgeschlossen sind.
shutdown
-Event¶
Um eine Funktion hinzuzufügen, die beim Herunterfahren der Anwendung ausgeführt werden soll, deklarieren Sie sie mit dem Event shutdown
:
from fastapi import FastAPI
app = FastAPI()
@app.on_event("shutdown")
def shutdown_event():
with open("log.txt", mode="a") as log:
log.write("Application shutdown")
@app.get("/items/")
async def read_items():
return [{"name": "Foo"}]
Hier schreibt die shutdown
-Eventhandler-Funktion eine Textzeile "Application shutdown"
in eine Datei log.txt
.
Info
In der Funktion open()
bedeutet mode="a"
„append“ („anhängen“), sodass die Zeile nach dem, was sich in dieser Datei befindet, hinzugefügt wird, ohne den vorherigen Inhalt zu überschreiben.
Tipp
Beachten Sie, dass wir in diesem Fall eine Standard-Python-Funktion open()
verwenden, die mit einer Datei interagiert.
Es handelt sich also um I/O (Input/Output), welches „Warten“ erfordert, bis Dinge auf die Festplatte geschrieben werden.
Aber open()
verwendet nicht async
und await
.
Daher deklarieren wir die Eventhandler-Funktion mit Standard-def
statt mit async def
.
startup
und shutdown
zusammen¶
Es besteht eine hohe Wahrscheinlichkeit, dass die Logik für Ihr Hochfahren und Herunterfahren miteinander verknüpft ist. Vielleicht möchten Sie etwas beginnen und es dann beenden, eine Ressource laden und sie dann freigeben usw.
Bei getrennten Funktionen, die keine gemeinsame Logik oder Variablen haben, ist dies schwieriger, da Sie Werte in globalen Variablen speichern oder ähnliche Tricks verwenden müssen.
Aus diesem Grund wird jetzt empfohlen, stattdessen lifespan
wie oben erläutert zu verwenden.
Technische Details¶
Nur ein technisches Detail für die neugierigen Nerds. 🤓
In der technischen ASGI-Spezifikation ist dies Teil des Lifespan Protokolls und definiert Events namens startup
und shutdown
.
Info
Weitere Informationen zu Starlettes lifespan
-Handlern finden Sie in Starlettes Lifespan-Dokumentation.
Einschließlich, wie man Lifespan-Zustand handhabt, der in anderen Bereichen Ihres Codes verwendet werden kann.
Unteranwendungen¶
🚨 Beachten Sie, dass diese Lifespan-Events (Hochfahren und Herunterfahren) nur für die Hauptanwendung ausgeführt werden, nicht für Unteranwendungen – Mounts.