コンテンツへスキップ

yield を使用した依存関係

FastAPI は、終了後にいくつかの追加手順を実行する依存関係をサポートしています。

これを行うには、return の代わりに yield を使用し、その後に追加の手順 (コード) を記述します。

ヒント

依存関係ごとに yield を 1 回だけ使用するようにしてください。

技術的な詳細

以下のいずれかと一緒に使用できる有効な関数はすべて

は、FastAPI 依存関係として使用できます。

実際、FastAPI は内部的にこれら 2 つのデコレータを使用しています。

yield を使用したデータベース依存関係

たとえば、これを使用してデータベースセッションを作成し、終了後に閉じることもできます。

レスポンスが作成される前に実行されるのは、yield ステートメントまでのコードのみです。

async def get_db():
    db = DBSession()
    try:
        yield db
    finally:
        db.close()

yield された値が、パス操作 およびその他の依存関係に注入される値です。

async def get_db():
    db = DBSession()
    try:
        yield db
    finally:
        db.close()

yield ステートメントに続くコードは、レスポンスの作成後に実行されますが、送信前です。

async def get_db():
    db = DBSession()
    try:
        yield db
    finally:
        db.close()

ヒント

async 関数または通常の関数を使用できます。

FastAPI は、通常の依存関係と同様に、それぞれの関数を適切に処理します。

yieldtry を使用した依存関係

yield を使用する依存関係で try ブロックを使用すると、依存関係の使用時にスローされた例外を受け取ることができます。

たとえば、途中のどこかで、別の依存関係やパス操作で、データベーストランザクションが「ロールバック」したり、その他のエラーが発生した場合、依存関係で例外を受け取ります。

したがって、except SomeException を使用して、依存関係内でその特定の例外を探すことができます。

同様に、finally を使用して、例外の有無にかかわらず、終了ステップが確実に実行されるようにすることができます。

async def get_db():
    db = DBSession()
    try:
        yield db
    finally:
        db.close()

yield を使用したサブ依存関係

サブ依存関係と、任意のサイズと形状のサブ依存関係の「ツリー」を持つことができ、それらのいずれかまたはすべてが yield を使用できます。

FastAPI は、yield を使用する各依存関係の「終了コード」が正しい順序で実行されることを保証します。

たとえば、dependency_cdependency_b に依存し、dependency_bdependency_a に依存できます。

from typing import Annotated

from fastapi import Depends


async def dependency_a():
    dep_a = generate_dep_a()
    try:
        yield dep_a
    finally:
        dep_a.close()


async def dependency_b(dep_a: Annotated[DepA, Depends(dependency_a)]):
    dep_b = generate_dep_b()
    try:
        yield dep_b
    finally:
        dep_b.close(dep_a)


async def dependency_c(dep_b: Annotated[DepB, Depends(dependency_b)]):
    dep_c = generate_dep_c()
    try:
        yield dep_c
    finally:
        dep_c.close(dep_b)
🤓 その他のバージョンとバリアント
from fastapi import Depends
from typing_extensions import Annotated


async def dependency_a():
    dep_a = generate_dep_a()
    try:
        yield dep_a
    finally:
        dep_a.close()


async def dependency_b(dep_a: Annotated[DepA, Depends(dependency_a)]):
    dep_b = generate_dep_b()
    try:
        yield dep_b
    finally:
        dep_b.close(dep_a)


async def dependency_c(dep_b: Annotated[DepB, Depends(dependency_b)]):
    dep_c = generate_dep_c()
    try:
        yield dep_c
    finally:
        dep_c.close(dep_b)

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from fastapi import Depends


async def dependency_a():
    dep_a = generate_dep_a()
    try:
        yield dep_a
    finally:
        dep_a.close()


async def dependency_b(dep_a=Depends(dependency_a)):
    dep_b = generate_dep_b()
    try:
        yield dep_b
    finally:
        dep_b.close(dep_a)


async def dependency_c(dep_b=Depends(dependency_b)):
    dep_c = generate_dep_c()
    try:
        yield dep_c
    finally:
        dep_c.close(dep_b)

そして、それらすべてが yield を使用できます。

この場合、dependency_c は、その終了コードを実行するために、dependency_b (ここでは dep_b と名付けられている) の値がまだ利用可能である必要があります。

そして、dependency_b は、その終了コードのために、dependency_a (ここでは dep_a と名付けられている) の値が利用可能である必要があります。

from typing import Annotated

from fastapi import Depends


async def dependency_a():
    dep_a = generate_dep_a()
    try:
        yield dep_a
    finally:
        dep_a.close()


async def dependency_b(dep_a: Annotated[DepA, Depends(dependency_a)]):
    dep_b = generate_dep_b()
    try:
        yield dep_b
    finally:
        dep_b.close(dep_a)


async def dependency_c(dep_b: Annotated[DepB, Depends(dependency_b)]):
    dep_c = generate_dep_c()
    try:
        yield dep_c
    finally:
        dep_c.close(dep_b)
🤓 その他のバージョンとバリアント
from fastapi import Depends
from typing_extensions import Annotated


async def dependency_a():
    dep_a = generate_dep_a()
    try:
        yield dep_a
    finally:
        dep_a.close()


async def dependency_b(dep_a: Annotated[DepA, Depends(dependency_a)]):
    dep_b = generate_dep_b()
    try:
        yield dep_b
    finally:
        dep_b.close(dep_a)


async def dependency_c(dep_b: Annotated[DepB, Depends(dependency_b)]):
    dep_c = generate_dep_c()
    try:
        yield dep_c
    finally:
        dep_c.close(dep_b)

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from fastapi import Depends


async def dependency_a():
    dep_a = generate_dep_a()
    try:
        yield dep_a
    finally:
        dep_a.close()


async def dependency_b(dep_a=Depends(dependency_a)):
    dep_b = generate_dep_b()
    try:
        yield dep_b
    finally:
        dep_b.close(dep_a)


async def dependency_c(dep_b=Depends(dependency_b)):
    dep_c = generate_dep_c()
    try:
        yield dep_c
    finally:
        dep_c.close(dep_b)

同様に、yield を使用する依存関係と return を使用する他の依存関係をいくつか持ち、それらの一部が他の依存関係の一部に依存するようにすることもできます。

また、複数の yield を使用する他の依存関係を必要とする単一の依存関係などを持つこともできます。

依存関係の任意の組み合わせを持つことができます。

FastAPI は、すべてが正しい順序で実行されることを保証します。

技術的な詳細

これは、Python の コンテキストマネージャ のおかげで機能します。

FastAPI は、これを実現するために内部的にそれらを使用しています。

yieldHTTPException を使用した依存関係

yield を使用する依存関係を使用し、例外をキャッチする try ブロックを持つことができることを見ました。

同様に、yield の後に、終了コードで HTTPException などを発生させることもできます。

ヒント

これはやや高度な手法であり、ほとんどの場合、実際に必要とすることはありません。なぜなら、アプリケーションコードの残りの部分 (たとえば、パス操作関数) から例外 (HTTPException を含む) を発生させることができるからです。

しかし、必要であれば、それはそこにあります。 🤓

from typing import Annotated

from fastapi import Depends, FastAPI, HTTPException

app = FastAPI()


data = {
    "plumbus": {"description": "Freshly pickled plumbus", "owner": "Morty"},
    "portal-gun": {"description": "Gun to create portals", "owner": "Rick"},
}


class OwnerError(Exception):
    pass


def get_username():
    try:
        yield "Rick"
    except OwnerError as e:
        raise HTTPException(status_code=400, detail=f"Owner error: {e}")


@app.get("/items/{item_id}")
def get_item(item_id: str, username: Annotated[str, Depends(get_username)]):
    if item_id not in data:
        raise HTTPException(status_code=404, detail="Item not found")
    item = data[item_id]
    if item["owner"] != username:
        raise OwnerError(username)
    return item
🤓 その他のバージョンとバリアント
from fastapi import Depends, FastAPI, HTTPException
from typing_extensions import Annotated

app = FastAPI()


data = {
    "plumbus": {"description": "Freshly pickled plumbus", "owner": "Morty"},
    "portal-gun": {"description": "Gun to create portals", "owner": "Rick"},
}


class OwnerError(Exception):
    pass


def get_username():
    try:
        yield "Rick"
    except OwnerError as e:
        raise HTTPException(status_code=400, detail=f"Owner error: {e}")


@app.get("/items/{item_id}")
def get_item(item_id: str, username: Annotated[str, Depends(get_username)]):
    if item_id not in data:
        raise HTTPException(status_code=404, detail="Item not found")
    item = data[item_id]
    if item["owner"] != username:
        raise OwnerError(username)
    return item

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from fastapi import Depends, FastAPI, HTTPException

app = FastAPI()


data = {
    "plumbus": {"description": "Freshly pickled plumbus", "owner": "Morty"},
    "portal-gun": {"description": "Gun to create portals", "owner": "Rick"},
}


class OwnerError(Exception):
    pass


def get_username():
    try:
        yield "Rick"
    except OwnerError as e:
        raise HTTPException(status_code=400, detail=f"Owner error: {e}")


@app.get("/items/{item_id}")
def get_item(item_id: str, username: str = Depends(get_username)):
    if item_id not in data:
        raise HTTPException(status_code=404, detail="Item not found")
    item = data[item_id]
    if item["owner"] != username:
        raise OwnerError(username)
    return item

例外をキャッチするために使用できる代替手段 (および場合によっては別の HTTPException を発生させることも) は、カスタム例外ハンドラ を作成することです。

yieldexcept を使用した依存関係

yield を使用する依存関係で except を使用して例外をキャッチし、それを再度発生させない場合 (または新しい例外を発生させない場合)、通常の Python と同じように、FastAPI は例外があったことに気付くことができません。

from typing import Annotated

from fastapi import Depends, FastAPI, HTTPException

app = FastAPI()


class InternalError(Exception):
    pass


def get_username():
    try:
        yield "Rick"
    except InternalError:
        print("Oops, we didn't raise again, Britney 😱")


@app.get("/items/{item_id}")
def get_item(item_id: str, username: Annotated[str, Depends(get_username)]):
    if item_id == "portal-gun":
        raise InternalError(
            f"The portal gun is too dangerous to be owned by {username}"
        )
    if item_id != "plumbus":
        raise HTTPException(
            status_code=404, detail="Item not found, there's only a plumbus here"
        )
    return item_id
🤓 その他のバージョンとバリアント
from fastapi import Depends, FastAPI, HTTPException
from typing_extensions import Annotated

app = FastAPI()


class InternalError(Exception):
    pass


def get_username():
    try:
        yield "Rick"
    except InternalError:
        print("Oops, we didn't raise again, Britney 😱")


@app.get("/items/{item_id}")
def get_item(item_id: str, username: Annotated[str, Depends(get_username)]):
    if item_id == "portal-gun":
        raise InternalError(
            f"The portal gun is too dangerous to be owned by {username}"
        )
    if item_id != "plumbus":
        raise HTTPException(
            status_code=404, detail="Item not found, there's only a plumbus here"
        )
    return item_id

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from fastapi import Depends, FastAPI, HTTPException

app = FastAPI()


class InternalError(Exception):
    pass


def get_username():
    try:
        yield "Rick"
    except InternalError:
        print("Oops, we didn't raise again, Britney 😱")


@app.get("/items/{item_id}")
def get_item(item_id: str, username: str = Depends(get_username)):
    if item_id == "portal-gun":
        raise InternalError(
            f"The portal gun is too dangerous to be owned by {username}"
        )
    if item_id != "plumbus":
        raise HTTPException(
            status_code=404, detail="Item not found, there's only a plumbus here"
        )
    return item_id

この場合、HTTPException などが発生していないことを考えると、クライアントは本来あるべき HTTP 500 Internal Server Error レスポンスを受け取りますが、サーバーはエラーが何であったかのログやその他の情報を持っていません。😱

yieldexcept を使用した依存関係では常に raise を使用する

yield を使用する依存関係で例外をキャッチした場合、別の HTTPException などが発生していない限り、元の例外を再発生させる必要があります。

raise を使用して同じ例外を再発生させることができます。

from typing import Annotated

from fastapi import Depends, FastAPI, HTTPException

app = FastAPI()


class InternalError(Exception):
    pass


def get_username():
    try:
        yield "Rick"
    except InternalError:
        print("We don't swallow the internal error here, we raise again 😎")
        raise


@app.get("/items/{item_id}")
def get_item(item_id: str, username: Annotated[str, Depends(get_username)]):
    if item_id == "portal-gun":
        raise InternalError(
            f"The portal gun is too dangerous to be owned by {username}"
        )
    if item_id != "plumbus":
        raise HTTPException(
            status_code=404, detail="Item not found, there's only a plumbus here"
        )
    return item_id
🤓 その他のバージョンとバリアント
from fastapi import Depends, FastAPI, HTTPException
from typing_extensions import Annotated

app = FastAPI()


class InternalError(Exception):
    pass


def get_username():
    try:
        yield "Rick"
    except InternalError:
        print("We don't swallow the internal error here, we raise again 😎")
        raise


@app.get("/items/{item_id}")
def get_item(item_id: str, username: Annotated[str, Depends(get_username)]):
    if item_id == "portal-gun":
        raise InternalError(
            f"The portal gun is too dangerous to be owned by {username}"
        )
    if item_id != "plumbus":
        raise HTTPException(
            status_code=404, detail="Item not found, there's only a plumbus here"
        )
    return item_id

ヒント

可能であれば`Annotated`バージョンを使用することをお勧めします。

from fastapi import Depends, FastAPI, HTTPException

app = FastAPI()


class InternalError(Exception):
    pass


def get_username():
    try:
        yield "Rick"
    except InternalError:
        print("We don't swallow the internal error here, we raise again 😎")
        raise


@app.get("/items/{item_id}")
def get_item(item_id: str, username: str = Depends(get_username)):
    if item_id == "portal-gun":
        raise InternalError(
            f"The portal gun is too dangerous to be owned by {username}"
        )
    if item_id != "plumbus":
        raise HTTPException(
            status_code=404, detail="Item not found, there's only a plumbus here"
        )
    return item_id

これでクライアントは同じ HTTP 500 Internal Server Error レスポンスを受け取りますが、サーバーにはログにカスタムの InternalError が記録されます。😎

yield を使用した依存関係の実行

実行シーケンスは、この図のようなものです。時間は上から下へ流れます。各列は、相互作用またはコードを実行する部分の 1 つです。

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,operation: Can raise exceptions, including HTTPException
    client ->> dep: Start request
    Note over dep: Run code up to yield
    opt raise Exception
        dep -->> handler: Raise Exception
        handler -->> client: HTTP error response
    end
    dep ->> operation: Run dependency, e.g. DB session
    opt raise
        operation -->> dep: Raise Exception (e.g. HTTPException)
        opt handle
            dep -->> dep: Can catch exception, raise a new HTTPException, raise other exception
        end
        handler -->> client: HTTP error response
    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 -->> tasks: Handle exceptions in the background task code
    end

情報

クライアントには1 つのレスポンスのみが送信されます。それはエラーレスポンスの 1 つであるか、パス操作からのレスポンスである可能性があります。

これらのレスポンスのいずれかが送信された後、他のレスポンスは送信できません。

ヒント

この図は HTTPException を示していますが、yield を使用する依存関係またはカスタム例外ハンドラでキャッチする他の例外も発生させることができます。

例外を発生させると、HTTPException を含む yield を持つ依存関係に渡されます。ほとんどの場合、適切に処理されるように、その同じ例外または yield を持つ依存関係からの新しい例外を再発生させたいと思うでしょう。

yieldHTTPExceptionexcept およびバックグラウンドタスクを使用した依存関係

警告

これらの技術的な詳細は、おそらく必要ありません。このセクションをスキップして、以下に進むことができます。

これらの詳細は、FastAPI の 0.106.0 より前のバージョンを使用しており、バックグラウンドタスクで yield を使用する依存関係からリソースを使用していた場合に主に役立ちます。

yieldexcept を使用した依存関係、技術的な詳細

FastAPI 0.110.0 より前は、yield を使用する依存関係を使用し、その依存関係で except で例外をキャッチし、例外を再度発生させなかった場合、例外は例外ハンドラーまたは内部サーバーエラーハンドラーに自動的に発生/転送されていました。

これは、ハンドラーなしで転送された例外 (内部サーバーエラー) からの処理されていないメモリ消費を修正し、通常の Python コードの動作と一貫性を持たせるために、バージョン 0.110.0 で変更されました。

バックグラウンドタスクと yield を使用した依存関係、技術的な詳細

FastAPI 0.106.0 より前は、yield の後に例外を発生させることはできませんでした。yield を使用する依存関係の終了コードは、レスポンスが送信された後に実行されたため、例外ハンドラは既に実行されていました。

これは、主に、バックグラウンドタスク内で依存関係によって「yield」された同じオブジェクトを使用できるようにするためにこのように設計されていました。なぜなら、終了コードはバックグラウンドタスクが終了した後に実行されるためです。

しかし、これは、yield を使用する依存関係でリソース (たとえばデータベース接続) を不必要に保持しながら、レスポンスがネットワークを介して伝送されるのを待つことを意味するため、FastAPI 0.106.0 で変更されました。

ヒント

さらに、バックグラウンドタスクは通常、独自のリソース (たとえば独自のデータベース接続) で個別に処理されるべき独立したロジックのセットです。

したがって、このようにすることで、おそらくよりクリーンなコードになります。

この動作に依存していた場合は、バックグラウンドタスク自体の内部でバックグラウンドタスクのリソースを作成し、yield を使用する依存関係のリソースに依存しないデータのみを内部的に使用する必要があります。

たとえば、同じデータベースセッションを使用する代わりに、バックグラウンドタスクの内部で新しいデータベースセッションを作成し、この新しいセッションを使用してデータベースからオブジェクトを取得します。そして、データベースからのオブジェクトをバックグラウンドタスク関数へのパラメーターとして渡す代わりに、そのオブジェクトの ID を渡し、バックグラウンドタスク関数内でオブジェクトを再度取得します。

コンテキストマネージャ

"コンテキストマネージャ" とは

"コンテキストマネージャ" は、with ステートメントで使用できる Python オブジェクトのいずれかです。

たとえば、with を使用してファイルを読み取ることができます。

with open("./somefile.txt") as f:
    contents = f.read()
    print(contents)

その下では、open("./somefile.txt") は「コンテキストマネージャ」と呼ばれるオブジェクトを作成します。

with ブロックが終了すると、例外があったとしてもファイルを確実に閉じます。

yield を使用する依存関係を作成すると、FastAPI は内部的にそれ用のコンテキストマネージャを作成し、それを他の関連ツールと組み合わせます。

yield を使用した依存関係でのコンテキストマネージャの使用

警告

これは、多かれ少なかれ「高度な」アイデアです。

FastAPI を始めたばかりの場合は、今はスキップすることをお勧めします。

Python では、__enter__()__exit__() の 2 つのメソッドを持つクラスを作成することで、コンテキストマネージャを作成できます。

また、依存関係関数内で with または async with ステートメントを使用して、yield を使用する FastAPI 依存関係内で使用することもできます。

class MySuperContextManager:
    def __init__(self):
        self.db = DBSession()

    def __enter__(self):
        return self.db

    def __exit__(self, exc_type, exc_value, traceback):
        self.db.close()


async def get_db():
    with MySuperContextManager() as db:
        yield db

ヒント

コンテキストマネージャを作成する別の方法は

単一の yield を持つ関数をデコレートするためにそれらを使用します。

これが、FastAPIyield を使用する依存関係に内部的に使用しているものです。

しかし、FastAPI の依存関係にデコレータを使用する必要はありません (また、使用すべきではありません)。

FastAPI が内部的に処理します。