ライフスパンイベント¶
アプリケーションが起動する前に実行されるロジック(コード)を定義できます。つまり、このコードは、アプリケーションがリクエストの受信を開始する前に一度だけ実行されます。
同様に、アプリケーションがシャットダウンするときに実行されるロジック(コード)を定義できます。この場合、このコードは、おそらく多くのリクエストを処理した後に一度だけ実行されます。
このコードは、アプリケーションがリクエストを受け取り始める前に実行され、リクエストの処理を終えた直後に実行されるため、アプリケーションのライフスパン全体をカバーします(「ライフスパン」という言葉はすぐに重要になります😉)。
これは、アプリケーション全体で使用する必要があり、リクエスト間で共有される、および/または後でクリーンアップする必要があるリソースを設定するのに非常に役立ちます。たとえば、データベース接続プールや、共有機械学習モデルのロードなどです。
ユースケース¶
まず、ユースケースの例から始め、これをどのように解決するかを見ていきましょう。
リクエストを処理するために使用したい機械学習モデルがあるとしましょう。🤖
同じモデルがリクエスト間で共有されるため、リクエストごとに1つのモデルや、ユーザーごとに1つのモデルなどのようなものではありません。
モデルのロードには、ディスクから大量のデータを読み取る必要があるため、非常に時間がかかる可能性があるとしましょう。そのため、リクエストごとに実行したくありません。
モジュール/ファイルの最上位レベルでロードすることもできますが、これは、単純な自動テストを実行している場合でも、モデルをロードすることを意味します。そのテストは、コードの独立した部分を実行できるようになる前に、モデルがロードされるのを待つ必要があるため、遅くなります。
それが私たちが解決することです。リクエストが処理される前にモデルをロードしますが、コードがロードされている間ではなく、アプリケーションがリクエストの受信を開始する直前のみにロードします。
ライフスパン¶
FastAPI
アプリのlifespan
パラメーターと「コンテキストマネージャー」を使用して、この起動およびシャットダウンロジックを定義できます(すぐにそれが何であるかをお見せします)。
まず例から始め、詳細を見ていきましょう。
次のように、yield
を含む非同期関数lifespan()
を作成します
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}
ここでは、yield
の前に機械学習モデルを辞書に入れることで、モデルをロードする高コストな起動処理をシミュレーションしています。このコードは、アプリケーションがリクエストを受け付け始める前、起動中に実行されます。
そして、yield
の直後には、モデルをアンロードします。このコードは、アプリケーションがリクエストの処理を完了した後、シャットダウンの直前に実行されます。これは、例えば、メモリやGPUなどのリソースを解放する可能性があります。
ヒント
シャットダウン
は、アプリケーションを停止するときに発生します。
新しいバージョンを開始する必要があるのかもしれませんし、単に実行に飽きたのかもしれません。🤷
ライフスパン関数¶
まず注目すべきは、yield
を持つ非同期関数を定義していることです。これは、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}
関数の最初の部分、yield
の前の部分は、アプリケーションが起動する前に実行されます。
そして、yield
の後の部分は、アプリケーションが終了した後に実行されます。
非同期コンテキストマネージャー¶
確認すると、関数は @asynccontextmanager
でデコレートされています。
これにより、関数は「非同期コンテキストマネージャー」と呼ばれるものに変換されます。
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}
Pythonにおけるコンテキストマネージャーは、例えば open()
がコンテキストマネージャーとして使用できるなど、with
ステートメントで使用できるものです。
with open("file.txt") as file:
file.read()
最近のバージョンのPythonでは、非同期コンテキストマネージャーもあります。これは async with
とともに使用します。
async with lifespan(app):
await do_stuff()
上記の様にコンテキストマネージャーまたは非同期コンテキストマネージャーを作成すると、with
ブロックに入る前に、yield
の前のコードが実行され、with
ブロックを抜けた後に、yield
の後のコードが実行されます。
上記のコード例では、それを直接使用するのではなく、FastAPIに渡して使用させます。
FastAPI
アプリの lifespan
パラメータは非同期コンテキストマネージャーを受け取るため、新しい lifespan
非同期コンテキストマネージャーを渡すことができます。
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}
代替イベント (非推奨)¶
警告
起動とシャットダウンを処理する推奨方法は、上記のように FastAPI
アプリの lifespan
パラメータを使用することです。lifespan
パラメータを指定した場合、startup
および shutdown
イベントハンドラーは呼び出されなくなります。すべて lifespan
を使用するか、すべてのイベントを使用するかのどちらかであり、両方を使用することはできません。
この部分はスキップしても構いません。
起動時およびシャットダウン時に実行されるこのロジックを定義する代替方法があります。
アプリケーションの起動前、またはアプリケーションのシャットダウン時に実行する必要があるイベントハンドラー(関数)を定義できます。
これらの関数は、async def
または通常の def
で宣言できます。
startup
イベント¶
アプリケーションの起動前に実行する必要がある関数を追加するには、イベント "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]
この場合、startup
イベントハンドラー関数は、アイテム「データベース」(単なる dict
)をいくつかの値で初期化します。
複数のイベントハンドラー関数を追加できます。
そして、すべての startup
イベントハンドラーが完了するまで、アプリケーションはリクエストの受信を開始しません。
shutdown
イベント¶
アプリケーションのシャットダウン時に実行する必要がある関数を追加するには、イベント "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"}]
ここでは、shutdown
イベントハンドラー関数は、テキスト行 "Application shutdown"
をファイル log.txt
に書き込みます。
情報
open()
関数では、mode="a"
は「追記」を意味するため、行は、以前の内容を上書きせずに、そのファイルの最後に追加されます。
ヒント
この場合、標準のPython open()
関数を使用してファイルとやり取りしていることに注意してください。
したがって、ディスクに書き込まれるのを「待つ」必要があるI/O(入出力)が含まれます。
しかし、open()
は async
と await
を使用しません。
そのため、イベントハンドラー関数は async def
の代わりに標準の def
で宣言します。
startup
と shutdown
を一緒に¶
起動とシャットダウンのロジックが関連している可能性は高く、何かを開始してから終了したり、リソースを取得してから解放したりする必要がある場合があります。
ロジックや変数を共有しない別々の関数でそれを行うと、グローバル変数や同様のトリックに値を格納する必要があるため、より困難になります。
そのため、上記で説明したように、代わりに lifespan
を使用することが推奨されるようになりました。
技術的な詳細¶
好奇心旺盛なオタクのための技術的な詳細です。🤓
内部的には、ASGIの技術仕様において、これはライフスパンプロトコルの一部であり、startup
と shutdown
というイベントを定義しています。
情報
StarletteのLifespanドキュメントで、Starletteの lifespan
ハンドラーについて詳しく読むことができます。
コードの他の領域で使用できるライフスパン状態を処理する方法も含みます。
サブアプリケーション¶
🚨 これらのライフスパンイベント(起動とシャットダウン)は、メインアプリケーションに対してのみ実行され、サブアプリケーション - マウントに対しては実行されないことに注意してください。