Así que estoy trabajando en mi primera API REST, así que pido disculpas de antemano si me perdí algo básico. De todos modos, tengo una función que toma una solicitud json de otro servidor, la procesa (hace una predicción basada en los datos) y devuelve otro json con los resultados. Me gustaría mantener un registro en el disco local del servidor de todas las solicitudes a este punto final junto con sus resultados, con fines de evaluación y para volver a entrenar el modelo. Sin embargo, con el fin de minimizar la latencia de devolver el resultado al usuario, me gustaría devolver primero los datos de respuesta y luego escribirlos en el disco local. No es obvio para mí cómo hacer esto correctamente, ya que el paradigma FastAPI requiere que el resultado de un método POST sea el valor de retorno de la función decorada, por lo que cualquier cosa que quiera hacer con los datos debe hacerse antes de que se devuelva.
A continuación se muestra un ejemplo funcional mínimo de lo que creo que es mi intento más cercano de hacerlo bien hasta el momento, usando un objeto personalizado con un decorador de log : mi idea era solo asignar el resultado al objeto de registro como un atributo de clase, luego usar otro método para escribirlo en el disco, pero no puedo averiguar cómo asegurarme de que esa función se llame después de get_data cada vez.
import json import uvicorn from fastapi import FastAPI, Request from functools import wraps from pydantic import BaseModel class Blob(BaseModel): id: int x: float def crunch_numbers(data: Blob) -> dict: # does some stuff return {'foo': 'bar'} class PostResponseLogger: def __init__(self) -> None: self.post_result = None def log(self, func, *args, **kwargs): @wraps(func) def func_to_log(*args, **kwargs): post_result = func(*args, **kwargs) self.post_result = post_result # how can this be done outside of this function ??? self.write_data() return post_result return func_to_log def write_data(self): if self.post_result: with open('output.json', 'w') as f: json.dump(self.post_result, f) def main(): app = FastAPI() logger = PostResponseLogger() @app.post('/get_data/') @logger.log def get_data(input_json: dict, request: Request): result = crunch_numbers(input_json) return result uvicorn.run(app=app) if __name__ == '__main__': main() Básicamente, mi pregunta se reduce a: "¿hay alguna manera, en la clase PostResponseLogger , de llamar automáticamente a self.write_data después de cada llamada a self.log ?", pero si estoy usando el enfoque equivocado, cualquier otra sugerencia es tambien bienvenido :)
Podría tener una background task para ese propósito. Una tarea en segundo plano "se ejecutará solo una vez que se haya enviado la respuesta" (según la documentación de Starlette ). Puede definir una función de tarea para que se ejecute en segundo plano para escribir los datos de registro, como se muestra a continuación:
def write_log_data(): logger.write_data() Defina un parámetro con una declaración de tipo de BackgroundTasks a su route , y dentro de la route , pase la función de tarea (arriba) al objeto background_tasks con el método add_task() :
from fastapi import BackgroundTasks @app.post('/get_data/') @logger.log def get_data(input_json: dict, request: Request, background_tasks: BackgroundTasks): result = crunch_numbers(input_json) background_tasks.add_task(write_log_data) return result Se podría aplicar el mismo principio si se usara un middleware para capturar y registrar los datos de respuesta, como se describe aquí . Como referencia futura, si usted (o cualquier otra persona) alguna vez necesita usar la sintaxis async/await y se encuentra con problemas de concurrencia (mientras realiza algunos cálculos pesados en segundo plano), consulte la documentación aquí , así como esta y esta respuesta .