13 KiB
yieldを持つ依存関係
FastAPIは、いくつかの終了後の追加のステップを行う依存関係をサポートしています。
これを行うには、returnの代わりにyieldを使い、その後に追加のステップを書きます。
!!! tip "豆知識"
yieldは必ず一度だけ使用するようにしてください。
!!! info "情報" これを動作させるには、Python 3.7 以上を使用するか、Python 3.6 では"backports"をインストールする必要があります:
```
pip install async-exit-stack async-generator
```
これにより<a href="https://github.com/sorcio/async_exit_stack" class="external-link" target="_blank">async-exit-stack</a>と<a href="https://github.com/python-trio/async_generator" class="external-link" target="_blank">async-generator</a>がインストールされます。
!!! note "技術詳細" 以下と一緒に使用できる関数なら何でも有効です:
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager" class="external-link" target="_blank">`@contextlib.contextmanager`</a>または
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager" class="external-link" target="_blank">`@contextlib.asynccontextmanager`</a>
これらは **FastAPI** の依存関係として使用するのに有効です。
実際、FastAPIは内部的にこれら2つのデコレータを使用しています。
yieldを持つデータベースの依存関係
例えば、これを使ってデータベースセッションを作成し、終了後にそれを閉じることができます。
レスポンスを送信する前にyield文を含む前のコードのみが実行されます。
{!../../../docs_src/dependencies/tutorial007.py!}
生成された値は、path operationsや他の依存関係に注入されるものです:
{!../../../docs_src/dependencies/tutorial007.py!}
yield文に続くコードは、レスポンスが送信された後に実行されます:
{!../../../docs_src/dependencies/tutorial007.py!}
!!! tip "豆知識"
asyncや通常の関数を使用することができます。
**FastAPI** は、通常の依存関係と同じように、それぞれで正しいことを行います。
yieldとtryを持つ依存関係
yieldを持つ依存関係でtryブロックを使用した場合、その依存関係を使用した際に発生した例外を受け取ることになります。
例えば、途中のどこかの時点で、別の依存関係やpath operationの中で、データベーストランザクションを「ロールバック」したり、その他のエラーを作成したりするコードがあった場合、依存関係の中で例外を受け取ることになります。
そのため、依存関係の中にある特定の例外をexcept SomeExceptionで探すことができます。
同様に、finallyを用いて例外があったかどうかにかかわらず、終了ステップを確実に実行することができます。
{!../../../docs_src/dependencies/tutorial007.py!}
yieldを持つサブ依存関係
任意の大きさや形のサブ依存関係やサブ依存関係の「ツリー」を持つことができ、その中でyieldを使用することができます。
FastAPI は、yieldを持つ各依存関係の「終了コード」が正しい順番で実行されていることを確認します。
例えば、dependency_cはdependency_bとdependency_bに依存するdependency_aに、依存することができます:
{!../../../docs_src/dependencies/tutorial008.py!}
そして、それらはすべてyieldを使用することができます。
この場合、dependency_cは終了コードを実行するために、dependency_b(ここではdep_bという名前)の値がまだ利用可能である必要があります。
そして、dependency_bはdependency_a(ここではdep_aという名前)の値を終了コードで利用できるようにする必要があります。
{!../../../docs_src/dependencies/tutorial008.py!}
同様に、yieldとreturnが混在した依存関係を持つこともできます。
また、単一の依存関係を持っていて、yieldなどの他の依存関係をいくつか必要とすることもできます。
依存関係の組み合わせは自由です。
FastAPI は、全てが正しい順序で実行されていることを確認します。
!!! note "技術詳細" これはPythonのContext Managersのおかげで動作します。
**FastAPI** はこれを実現するために内部的に使用しています。
yieldとHTTPExceptionを持つ依存関係
yieldと例外をキャッチするtryブロックを持つことができる依存関係を使用することができることがわかりました。
yieldの後の終了コードでHTTPExceptionなどを発生させたくなるかもしれません。しかしそれはうまくいきません
yieldを持つ依存関係の終了コードは例外ハンドラ{.internal-link target=_blank}の後に実行されます。依存関係によって投げられた例外を終了コード(yieldの後)でキャッチするものはなにもありません。
つまり、yieldの後にHTTPExceptionを発生させた場合、HTTTPExceptionをキャッチしてHTTP 400のレスポンスを返すデフォルトの(あるいは任意のカスタムの)例外ハンドラは、その例外をキャッチすることができなくなります。
これは、依存関係に設定されているもの(例えば、DBセッション)を、例えば、バックグラウンドタスクで使用できるようにするものです。
バックグラウンドタスクはレスポンスが送信された後に実行されます。そのため、すでに送信されているレスポンスを変更する方法すらないので、HTTPExceptionを発生させる方法はありません。
しかし、バックグラウンドタスクがDBエラーを発生させた場合、少なくともyieldで依存関係のセッションをロールバックしたり、きれいに閉じたりすることができ、エラーをログに記録したり、リモートのトラッキングシステムに報告したりすることができます。
例外が発生する可能性があるコードがある場合は、最も普通の「Python流」なことをして、コードのその部分にtryブロックを追加してください。
レスポンスを返したり、レスポンスを変更したり、HTTPExceptionを発生させたりする前に処理したいカスタム例外がある場合は、カスタム例外ハンドラ{.internal-link target=_blank}を作成してください。
!!! tip "豆知識"
HTTPExceptionを含む例外は、yieldの前でも発生させることができます。ただし、後ではできません。
実行の順序は多かれ少なかれ以下の図のようになります。時間は上から下へと流れていきます。そして、各列はコードを相互作用させたり、実行したりしている部分の一つです。
sequenceDiagram
participant client as Client
participant handler as Exception handler
participant dep as Dep with yield
participant operation as Path Operation
participant tasks as Background tasks
Note over client,tasks: Can raise exception for dependency, handled after response is sent
Note over client,operation: Can raise HTTPException and can change the response
client ->> dep: Start request
Note over dep: Run code up to yield
opt raise
dep -->> handler: Raise HTTPException
handler -->> client: HTTP error response
dep -->> dep: Raise other exception
end
dep ->> operation: Run dependency, e.g. DB session
opt raise
operation -->> handler: Raise HTTPException
handler -->> client: HTTP error response
operation -->> dep: Raise other exception
end
operation ->> client: Return response to client
Note over client,operation: Response is already sent, can't change it anymore
opt Tasks
operation -->> tasks: Send background tasks
end
opt Raise other exception
tasks -->> dep: Raise other exception
end
Note over dep: After yield
opt Handle other exception
dep -->> dep: Handle exception, can't change response. E.g. close DB session.
end
!!! info "情報" 1つのレスポンス だけがクライアントに送信されます。それはエラーレスポンスの一つかもしれませんし、path operationからのレスポンスかもしれません。
いずれかのレスポンスが送信された後、他のレスポンスを送信することはできません。
!!! tip "豆知識"
この図はHTTPExceptionを示していますが、カスタム例外ハンドラ{.internal-link target=_blank}を作成することで、他の例外を発生させることもできます。そして、その例外は依存関係の終了コードではなく、そのカスタム例外ハンドラによって処理されます。
しかし例外ハンドラで処理されない例外を発生させた場合は、依存関係の終了コードで処理されます。
コンテキストマネージャ
「コンテキストマネージャ」とは
「コンテキストマネージャ」とは、with文の中で使用できるPythonオブジェクトのことです。
例えば、ファイルを読み込むにはwithを使用することができます:
with open("./somefile.txt") as f:
contents = f.read()
print(contents)
その後のopen("./somefile.txt")は「コンテキストマネージャ」と呼ばれるオブジェクトを作成します。
withブロックが終了すると、例外があったとしてもファイルを確かに閉じます。
yieldを依存関係を作成すると、FastAPI は内部的にそれをコンテキストマネージャに変換し、他の関連ツールと組み合わせます。
yieldを持つ依存関係でのコンテキストマネージャの使用
!!! warning "注意" これは多かれ少なかれ、「高度な」発想です。
**FastAPI** を使い始めたばかりの方は、とりあえずスキップした方がよいかもしれません。
Pythonでは、以下の2つのメソッドを持つクラスを作成する: __enter__()と__exit__()ことでコンテキストマネージャを作成することができます。
また、依存関数の中でwithやasync with文を使用することによってyieldを持つ FastAPI の依存関係の中でそれらを使用することができます:
{!../../../docs_src/dependencies/tutorial010.py!}
!!! tip "豆知識" コンテキストマネージャを作成するもう一つの方法はwithです:
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager" class="external-link" target="_blank">`@contextlib.contextmanager`</a> または
* <a href="https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager" class="external-link" target="_blank">`@contextlib.asynccontextmanager`</a>
これらを使って、関数を単一の`yield`でデコレートすることができます。
これは **FastAPI** が内部的に`yield`を持つ依存関係のために使用しているものです。
しかし、FastAPIの依存関係にデコレータを使う必要はありません(そして使うべきではありません)。
FastAPIが内部的にやってくれます。