Eventos de Lifespan¶
Puedes definir l贸gica (c贸digo) que deber铆a ser ejecutada antes de que la aplicaci贸n inicie. Esto significa que este c贸digo ser谩 ejecutado una vez, antes de que la aplicaci贸n comience a recibir requests.
De la misma manera, puedes definir l贸gica (c贸digo) que deber铆a ser ejecutada cuando la aplicaci贸n est茅 cerr谩ndose. En este caso, este c贸digo ser谩 ejecutado una vez, despu茅s de haber manejado posiblemente muchos requests.
Debido a que este c贸digo se ejecuta antes de que la aplicaci贸n comience a tomar requests, y justo despu茅s de que termine de manejarlos, cubre todo el lifespan de la aplicaci贸n (la palabra "lifespan" ser谩 importante en un momento 馃槈).
Esto puede ser muy 煤til para configurar recursos que necesitas usar para toda la app, y que son compartidos entre requests, y/o que necesitas limpiar despu茅s. Por ejemplo, un pool de conexiones a una base de datos, o cargando un modelo de machine learning compartido.
Caso de Uso¶
Empecemos con un ejemplo de caso de uso y luego veamos c贸mo resolverlo con esto.
Imaginemos que tienes algunos modelos de machine learning que quieres usar para manejar requests. 馃
Los mismos modelos son compartidos entre requests, por lo que no es un modelo por request, o uno por usuario o algo similar.
Imaginemos que cargar el modelo puede tomar bastante tiempo, porque tiene que leer muchos datos del disco. Entonces no quieres hacerlo para cada request.
Podr铆as cargarlo en el nivel superior del m贸dulo/archivo, pero eso tambi茅n significar铆a que cargar铆a el modelo incluso si solo est谩s ejecutando una simple prueba automatizada, entonces esa prueba ser铆a lenta porque tendr铆a que esperar a que el modelo se cargue antes de poder ejecutar una parte independiente del c贸digo.
Eso es lo que resolveremos, vamos a cargar el modelo antes de que los requests sean manejados, pero solo justo antes de que la aplicaci贸n comience a recibir requests, no mientras el c贸digo se est谩 cargando.
Lifespan¶
Puedes definir esta l贸gica de startup y shutdown usando el par谩metro lifespan de la app de FastAPI, y un "context manager" (te mostrar茅 lo que es en un momento).
Comencemos con un ejemplo y luego ve谩moslo en detalle.
Creamos una funci贸n as铆ncrona lifespan() con yield as铆:
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}
Aqu铆 estamos simulando la operaci贸n costosa de startup de cargar el modelo poniendo la funci贸n del (falso) modelo en el diccionario con modelos de machine learning antes del yield. Este c贸digo ser谩 ejecutado antes de que la aplicaci贸n comience a tomar requests, durante el startup.
Y luego, justo despu茅s del yield, quitaremos el modelo de memoria. Este c贸digo ser谩 ejecutado despu茅s de que la aplicaci贸n termine de manejar requests, justo antes del shutdown. Esto podr铆a, por ejemplo, liberar recursos como la memoria o una GPU.
Consejo
El shutdown ocurrir铆a cuando est谩s deteniendo la aplicaci贸n.
Quiz谩s necesites iniciar una nueva versi贸n, o simplemente te cansaste de ejecutarla. 馃し
Funci贸n de Lifespan¶
Lo primero que hay que notar es que estamos definiendo una funci贸n as铆ncrona con yield. Esto es muy similar a las Dependencias con 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}
La primera parte de la funci贸n, antes del yield, ser谩 ejecutada antes de que la aplicaci贸n comience.
Y la parte despu茅s del yield ser谩 ejecutada despu茅s de que la aplicaci贸n haya terminado.
Async Context Manager¶
Si revisas, la funci贸n est谩 decorada con un @asynccontextmanager.
Eso convierte a la funci贸n en algo llamado un "async context manager".
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}
Un context manager en Python es algo que puedes usar en un statement with, por ejemplo, open() puede ser usado como un context manager:
with open("file.txt") as file:
file.read()
En versiones recientes de Python, tambi茅n hay un async context manager. Lo usar铆as con async with:
async with lifespan(app):
await do_stuff()
Cuando creas un context manager o un async context manager como arriba, lo que hace es que, antes de entrar al bloque with, ejecutar谩 el c贸digo antes del yield, y al salir del bloque with, ejecutar谩 el c贸digo despu茅s del yield.
En nuestro ejemplo de c贸digo arriba, no lo usamos directamente, pero se lo pasamos a FastAPI para que lo use.
El par谩metro lifespan de la app de FastAPI toma un async context manager, por lo que podemos pasar nuestro nuevo lifespan async context manager a 茅l.
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}
Eventos Alternativos (obsoleto)¶
Advertencia
La forma recomendada de manejar el startup y el shutdown es usando el par谩metro lifespan de la app de FastAPI como se describi贸 arriba. Si proporcionas un par谩metro lifespan, los manejadores de eventos startup y shutdown ya no ser谩n llamados. Es solo lifespan o solo los eventos, no ambos.
Probablemente puedas saltarte esta parte.
Hay una forma alternativa de definir esta l贸gica para ser ejecutada durante el startup y durante el shutdown.
Puedes definir manejadores de eventos (funciones) que necesitan ser ejecutadas antes de que la aplicaci贸n se inicie, o cuando la aplicaci贸n se est谩 cerrando.
Estas funciones pueden ser declaradas con async def o def normal.
Evento startup¶
Para a帽adir una funci贸n que deber铆a ejecutarse antes de que la aplicaci贸n inicie, decl谩rala con el evento "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]
En este caso, la funci贸n manejadora del evento startup inicializar谩 los 铆tems de la "base de datos" (solo un dict) con algunos valores.
Puedes a帽adir m谩s de un manejador de eventos.
Y tu aplicaci贸n no comenzar谩 a recibir requests hasta que todos los manejadores de eventos startup hayan completado.
Evento shutdown¶
Para a帽adir una funci贸n que deber铆a ejecutarse cuando la aplicaci贸n se est茅 cerrando, decl谩rala con el evento "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"}]
Aqu铆, la funci贸n manejadora del evento shutdown escribir谩 una l铆nea de texto "Application shutdown" a un archivo log.txt.
Informaci贸n
En la funci贸n open(), el mode="a" significa "a帽adir", por lo tanto, la l铆nea ser谩 a帽adida despu茅s de lo que sea que est茅 en ese archivo, sin sobrescribir el contenido anterior.
Consejo
Nota que en este caso estamos usando una funci贸n est谩ndar de Python open() que interact煤a con un archivo.
Entonces, involucra I/O (entrada/salida), que requiere "esperar" para que las cosas se escriban en el disco.
Pero open() no usa async y await.
Por eso, declaramos la funci贸n manejadora del evento con def est谩ndar en vez de async def.
startup y shutdown juntos¶
Hay una gran posibilidad de que la l贸gica para tu startup y shutdown est茅 conectada, podr铆as querer iniciar algo y luego finalizarlo, adquirir un recurso y luego liberarlo, etc.
Hacer eso en funciones separadas que no comparten l贸gica o variables juntas es m谩s dif铆cil ya que necesitar铆as almacenar valores en variables globales o trucos similares.
Debido a eso, ahora se recomienda en su lugar usar el lifespan como se explic贸 arriba.
Detalles T茅cnicos¶
Solo un detalle t茅cnico para los nerds curiosos. 馃
Por debajo, en la especificaci贸n t茅cnica ASGI, esto es parte del Protocolo de Lifespan, y define eventos llamados startup y shutdown.
Informaci贸n
Puedes leer m谩s sobre los manejadores lifespan de Starlette en la documentaci贸n de Lifespan de Starlette.
Incluyendo c贸mo manejar el estado de lifespan que puede ser usado en otras 谩reas de tu c贸digo.
Sub Aplicaciones¶
馃毃 Ten en cuenta que estos eventos de lifespan (startup y shutdown) solo ser谩n ejecutados para la aplicaci贸n principal, no para Sub Aplicaciones - Mounts.