Иногда вам нужно определить логику, которая должна исполняться перед запуском приложения.
Это означает, что код будет исполнен один раз - еще до того, как ваше приложение начнет принимать сообщения.
Также, у вас может возникнуть необходимость завершить некоторые процессы после остановки приложения. В этом случае, ваш код также будет выполнен ровно один раз:
но уже после завершения работы основного приложения.
Поскольку этот код исполняется перед запуском приложения и после его остановки, он покрывает весь жизненный цикл (lifespan) приложения.
Это может быть очень полезно для инициализации настроек вашего приложения при старте, поднятия пула соединений к базе данных или запуска моделей машинного обучения.
Давайте представим, что ваше приложение использует pydantic в качестве менеджера ваших настроек.
Я крайне рекомендую использовать pydantic для этих целей, т.к. эта зависимость уже используется в Propan
и вам не придется устанавливать дополнительный пакет
Также, давайте представим, что у вас есть несколько .env, .env.development, .env.test, .env.production файлов с настройками вашего приложения,
и вы хотите переключать их при запуске без изменений в коде.
Теперь давайте представим, что у нас есть модель машинного обучения, которая должна обрабатывать сообщения из какого-либо брокера.
Обычна инициализация таких моделей занимает продолжительное время. Разумно будет сделать это при старте приложения, а не при обработке каждого сообщения.
Вы можете инициализировать вашу модель где-то вверху вашего модуля/файла. Однако, в таком случае, этот код будет запущен даже просто в случае импортирования
этого модуля, например, при тестировании. Вряд ли вы хотите запускать вашу модель на каждый запуск тестов...
Поэтому, стоит инициализированить модель в хуке @app.on_startup.
Также, мы не хотим, чтобы модель окончила свою работу при остановке приложения некорректно. Чтобы избежать этого, нам понадобиться хук @app.on_shutdown
frompropanimportPropanApp,Context,RedisBrokerfrompropan.annotationsimportContextRepobroker=RedisBroker("redis://localhost:6379")app=PropanApp(broker)ml_models={}# fake ML modeldeffake_answer_to_everything_ml_model(x:float):returnx*42@app.on_startupasyncdefsetup_model(context:ContextRepo):# Load the ML modelml_models["answer_to_everything"]=fake_answer_to_everything_ml_modelcontext.set_global("model",ml_models)@app.on_shutdownasyncdefshutdown_model(model:dict=Context()):# Clean up the ML models and release the resourcesmodel.clear()@broker.handle("test")asyncdefpredict(x:float,model=Context()):result=model["answer_to_everything"](x)return{"result":result}
frompropanimportPropanApp,Context,RabbitBrokerfrompropan.annotationsimportContextRepobroker=RabbitBroker("amqp://guest:guest@localhost:5672/")app=PropanApp(broker)ml_models={}# fake ML modeldeffake_answer_to_everything_ml_model(x:float):returnx*42@app.on_startupasyncdefsetup_model(context:ContextRepo):# Load the ML modelml_models["answer_to_everything"]=fake_answer_to_everything_ml_modelcontext.set_global("model",ml_models)@app.on_shutdownasyncdefshutdown_model(model:dict=Context()):# Clean up the ML models and release the resourcesmodel.clear()@broker.handle("test")asyncdefpredict(x:float,model=Context()):result=model["answer_to_everything"](x)return{"result":result}
frompropanimportPropanApp,Context,KafkaBrokerfrompropan.annotationsimportContextRepobroker=KafkaBroker("localhost:9092")app=PropanApp(broker)ml_models={}# fake ML modeldeffake_answer_to_everything_ml_model(x:float):returnx*42@app.on_startupasyncdefsetup_model(context:ContextRepo):# Load the ML modelml_models["answer_to_everything"]=fake_answer_to_everything_ml_modelcontext.set_global("model",ml_models)@app.on_shutdownasyncdefshutdown_model(model:dict=Context()):# Clean up the ML models and release the resourcesmodel.clear()@broker.handle("test")asyncdefpredict(x:float,model=Context()):result=model["answer_to_everything"](x)return{"result":result}
frompropanimportPropanApp,Context,SQSBrokerfrompropan.annotationsimportContextRepobroker=SQSBroker("http://localhost:9324",...)app=PropanApp(broker)ml_models={}# fake ML modeldeffake_answer_to_everything_ml_model(x:float):returnx*42@app.on_startupasyncdefsetup_model(context:ContextRepo):# Load the ML modelml_models["answer_to_everything"]=fake_answer_to_everything_ml_modelcontext.set_global("model",ml_models)@app.on_shutdownasyncdefshutdown_model(model:dict=Context()):# Clean up the ML models and release the resourcesmodel.clear()@broker.handle("test")asyncdefpredict(x:float,model=Context()):result=model["answer_to_everything"](x)return{"result":result}
frompropanimportPropanApp,Context,NatsBrokerfrompropan.annotationsimportContextRepobroker=NatsBroker("nats://localhost:4222")app=PropanApp(broker)ml_models={}# fake ML modeldeffake_answer_to_everything_ml_model(x:float):returnx*42@app.on_startupasyncdefsetup_model(context:ContextRepo):# Load the ML modelml_models["answer_to_everything"]=fake_answer_to_everything_ml_modelcontext.set_global("model",ml_models)@app.on_shutdownasyncdefshutdown_model(model:dict=Context()):# Clean up the ML models and release the resourcesmodel.clear()@broker.handle("test")asyncdefpredict(x:float,model=Context()):result=model["answer_to_everything"](x)return{"result":result}
В асинхронной версии приложения в качестве хуков могут использоваться как асинхронные, так и синхронные методы.
В синхронной версии доступны только синхронные методы.
Хуки @app.on_startup вызываются ДО запуска брокера приложением. Хуки @app.after_shutdown запускаются ПОСЛЕ остановки брокера.
Если же вы хотите совершить какие-то действия ПОСЛЕ инициализации брокера: отправить сообщения, инициализировать объекты и т.д., вам стоит использовать хук @app.after_startup.