コンテンツにスキップ

大規模アプリケーション - 複数ファイル

🌐 AI と人間による翻訳

この翻訳は、人間のガイドに基づいて AI によって作成されました。🤝

原文の意図を取り違えていたり、不自然な表現になっている可能性があります。🤖

AI LLM をより適切に誘導するのを手伝う ことで、この翻訳を改善できます。

英語版

アプリケーションや Web API を作る場合、すべてを1つのファイルに収められることはほとんどありません。

FastAPI は、柔軟性を保ったままアプリケーションを構造化できる便利なツールを提供します。

情報

Flask 出身であれば、Flask の Blueprint に相当します。

例のファイル構成

次のようなファイル構成があるとします:

.
├── app
│   ├── __init__.py
│   ├── main.py
│   ├── dependencies.py
│   └── routers
│   │   ├── __init__.py
│   │   ├── items.py
│   │   └── users.py
│   └── internal
│       ├── __init__.py
│       └── admin.py

豆知識

複数の __init__.py ファイルがあります: 各ディレクトリやサブディレクトリに1つずつです。

これにより、あるファイルから別のファイルへコードをインポートできます。

例えば、app/main.py では次のように書けます:

from app.routers import items
  • app ディレクトリはすべてを含みます。そして空のファイル app/__init__.py があり、「Python パッケージ」(「Python モジュール」の集合): app です。
  • app/main.py ファイルがあります。Python パッケージ(__init__.py のあるディレクトリ)の中にあるため、そのパッケージの「モジュール」: app.main です。
  • app/dependencies.py ファイルもあり、app/main.py と同様に「モジュール」: app.dependencies です。
  • app/routers/ サブディレクトリに別の __init__.py があるので、「Python サブパッケージ」: app.routers です。
  • app/routers/items.py はパッケージ app/routers/ 内のファイルなので、サブモジュール: app.routers.items です。
  • app/routers/users.py も同様で、別のサブモジュール: app.routers.users です。
  • app/internal/ サブディレクトリにも __init__.py があるので、別の「Python サブパッケージ」: app.internal です。
  • app/internal/admin.py は別のサブモジュール: app.internal.admin です。

同じファイル構成にコメントを付けると次のとおりです:

.
├── app                  # "app" is a Python package
   ├── __init__.py      # this file makes "app" a "Python package"
   ├── main.py          # "main" module, e.g. import app.main
   ├── dependencies.py  # "dependencies" module, e.g. import app.dependencies
   └── routers          # "routers" is a "Python subpackage"
      ├── __init__.py  # makes "routers" a "Python subpackage"
      ├── items.py     # "items" submodule, e.g. import app.routers.items
      └── users.py     # "users" submodule, e.g. import app.routers.users
   └── internal         # "internal" is a "Python subpackage"
       ├── __init__.py  # makes "internal" a "Python subpackage"
       └── admin.py     # "admin" submodule, e.g. import app.internal.admin

APIRouter

ユーザーだけを扱うファイルが /app/routers/users.py のサブモジュールだとします。

ユーザーに関連する path operations をほかのコードから分離して整理したいはずです。

ただし、同じ FastAPI アプリケーション / Web API(同じ「Python パッケージ」の一部)である点は変わりません。

そのモジュールで APIRouter を使って path operations を作成できます。

APIRouter のインポート

クラス FastAPI と同様にインポートし、「インスタンス」を作成します:

app/routers/users.py
from fastapi import APIRouter

router = APIRouter()


@router.get("/users/", tags=["users"])
async def read_users():
    return [{"username": "Rick"}, {"username": "Morty"}]


@router.get("/users/me", tags=["users"])
async def read_user_me():
    return {"username": "fakecurrentuser"}


@router.get("/users/{username}", tags=["users"])
async def read_user(username: str):
    return {"username": username}

APIRouter での path operations

これを使って path operations を宣言します。

使い方は FastAPI クラスと同じです:

app/routers/users.py
from fastapi import APIRouter

router = APIRouter()


@router.get("/users/", tags=["users"])
async def read_users():
    return [{"username": "Rick"}, {"username": "Morty"}]


@router.get("/users/me", tags=["users"])
async def read_user_me():
    return {"username": "fakecurrentuser"}


@router.get("/users/{username}", tags=["users"])
async def read_user(username: str):
    return {"username": username}

APIRouter は「ミニ FastAPI」のようなクラスと考えられます。

同じオプションがすべてサポートされています。

同じ parametersresponsesdependenciestags などが使えます。

豆知識

この例では変数名は router ですが、任意の名前を付けられます。

この APIRouter をメインの FastAPI アプリに取り込みますが、その前に依存関係と別の APIRouter を確認します。

依存関係

アプリケーションの複数箇所で使う依存関係が必要になります。

そのため、専用の dependencies モジュール(app/dependencies.py)に置きます。

ここではカスタムヘッダー X-Token を読む簡単な依存関係を使います:

app/dependencies.py
from typing import Annotated

from fastapi import Header, HTTPException


async def get_token_header(x_token: Annotated[str, Header()]):
    if x_token != "fake-super-secret-token":
        raise HTTPException(status_code=400, detail="X-Token header invalid")


async def get_query_token(token: str):
    if token != "jessica":
        raise HTTPException(status_code=400, detail="No Jessica token provided")
🤓 Other versions and variants
app/dependencies.py
from typing import Annotated

from fastapi import Header, HTTPException


async def get_token_header(x_token: Annotated[str, Header()]):
    if x_token != "fake-super-secret-token":
        raise HTTPException(status_code=400, detail="X-Token header invalid")


async def get_query_token(token: str):
    if token != "jessica":
        raise HTTPException(status_code=400, detail="No Jessica token provided")

Tip

Prefer to use the Annotated version if possible.

app/dependencies.py
from fastapi import Header, HTTPException


async def get_token_header(x_token: str = Header()):
    if x_token != "fake-super-secret-token":
        raise HTTPException(status_code=400, detail="X-Token header invalid")


async def get_query_token(token: str):
    if token != "jessica":
        raise HTTPException(status_code=400, detail="No Jessica token provided")

豆知識

この例を簡単にするために架空のヘッダーを使っています。

しかし実際には、組み込みの Security utilities を使う方が良い結果になります。

別モジュールでの APIRouter

アプリケーションの「items」を扱うエンドポイントが app/routers/items.py のモジュールにあるとします。

次の path operations があります:

  • /items/
  • /items/{item_id}

構造は app/routers/users.py と同じです。

しかし、もう少し賢くしてコードを少し簡潔にしたいところです。

このモジュールのすべての path operations には同じものがあると分かっています:

  • パスの prefix: /items
  • tags(1つのタグ: items
  • 追加の responses
  • dependencies: 先ほど作成した X-Token の依存関係が必要

そこで、各 path operation に個別に追加する代わりに、これらを APIRouter に追加できます。

app/routers/items.py
from fastapi import APIRouter, Depends, HTTPException

from ..dependencies import get_token_header

router = APIRouter(
    prefix="/items",
    tags=["items"],
    dependencies=[Depends(get_token_header)],
    responses={404: {"description": "Not found"}},
)


fake_items_db = {"plumbus": {"name": "Plumbus"}, "gun": {"name": "Portal Gun"}}


@router.get("/")
async def read_items():
    return fake_items_db


@router.get("/{item_id}")
async def read_item(item_id: str):
    if item_id not in fake_items_db:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"name": fake_items_db[item_id]["name"], "item_id": item_id}


@router.put(
    "/{item_id}",
    tags=["custom"],
    responses={403: {"description": "Operation forbidden"}},
)
async def update_item(item_id: str):
    if item_id != "plumbus":
        raise HTTPException(
            status_code=403, detail="You can only update the item: plumbus"
        )
    return {"item_id": item_id, "name": "The great Plumbus"}

path operation のパスは次のように / で始める必要があるため:

@router.get("/{item_id}")
async def read_item(item_id: str):
    ...

...prefix の末尾に / を含めてはいけません。

この場合の prefix は /items です。

また、tags のリストや追加の responses を、このルーターに含まれるすべての path operations に適用するよう追加できます。

さらに dependencies のリストを追加できます。これはこのルーター内のすべての path operations に追加され、それらへの各リクエストごとに実行・解決されます。

豆知識

path operation デコレータの依存関係 と同様に、path operation 関数には値は渡されない点に注意してください。

最終的に、item のパスは次のとおりになります:

  • /items/
  • /items/{item_id}

...意図したとおりです。

  • これらには、文字列 "items" を1つ含むタグのリストが付きます。
    • これらの「タグ」は、(OpenAPI を使う)自動インタラクティブドキュメントで特に有用です。
  • すべてに事前定義した responses が含まれます。
  • これらすべての path operations では、実行前に dependencies のリストが評価・実行されます。

豆知識

APIRouterdependencies を置くことで、path operations のグループ全体に認証を要求する、といった用途に使えます。個々の path operation に依存関係を追加していなくても構いません。

確認

prefixtagsresponsesdependencies の各パラメータは(ほかの多くのケースと同様に)コード重複を避けるための FastAPI の機能です。

依存関係をインポート

このコードはモジュール app.routers.items(ファイル app/routers/items.py)内にあります。

そして依存関係の関数はモジュール app.dependencies(ファイル app/dependencies.py)から取得する必要があります。

そこで、依存関係には .. を使った相対インポートを使います:

app/routers/items.py
from fastapi import APIRouter, Depends, HTTPException

from ..dependencies import get_token_header

router = APIRouter(
    prefix="/items",
    tags=["items"],
    dependencies=[Depends(get_token_header)],
    responses={404: {"description": "Not found"}},
)


fake_items_db = {"plumbus": {"name": "Plumbus"}, "gun": {"name": "Portal Gun"}}


@router.get("/")
async def read_items():
    return fake_items_db


@router.get("/{item_id}")
async def read_item(item_id: str):
    if item_id not in fake_items_db:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"name": fake_items_db[item_id]["name"], "item_id": item_id}


@router.put(
    "/{item_id}",
    tags=["custom"],
    responses={403: {"description": "Operation forbidden"}},
)
async def update_item(item_id: str):
    if item_id != "plumbus":
        raise HTTPException(
            status_code=403, detail="You can only update the item: plumbus"
        )
    return {"item_id": item_id, "name": "The great Plumbus"}

相対インポートの仕組み

豆知識

インポートの仕組みを十分理解している場合は、次の節に進んでください。

ドット1つ . を使うと、次のような意味になります:

from .dependencies import get_token_header

意味:

  • このモジュール(app/routers/items.py)が存在する同じパッケージ(ディレクトリ app/routers/)から開始し...
  • モジュール dependencies(仮想的には app/routers/dependencies.py)を探し...
  • そこから関数 get_token_header をインポートする。

しかしそのファイルは存在せず、実際の依存関係は app/dependencies.py にあります。

アプリ/ファイル構成がどうなっていたかを思い出してください:


ドット2つ .. を使うと、次のようになります:

from ..dependencies import get_token_header

意味:

  • このモジュール(app/routers/items.py)が存在する同じパッケージ(ディレクトリ app/routers/)から開始し...
  • 親パッケージ(ディレクトリ app/)に移動し...
  • そこでモジュール dependencies(ファイル app/dependencies.py)を探し...
  • そこから関数 get_token_header をインポートする。

これは正しく動作します! 🎉


同様に、ドット3つ ... を使うと:

from ...dependencies import get_token_header

意味:

  • このモジュール(app/routers/items.py)が存在する同じパッケージ(ディレクトリ app/routers/)から開始し...
  • 親パッケージ(ディレクトリ app/)に移動し...
  • さらにその親パッケージに移動しようとします(app は最上位なので親パッケージはありません 😱)...
  • そこでモジュール dependencies(ファイル app/dependencies.py)を探し...
  • そこから関数 get_token_header をインポートする。

これは app/ より上位のパッケージ(独自の __init__.py を持つ)を参照することになります。しかしそのようなものはありません。そのため、この例ではエラーになります。🚨

これで仕組みが分かったので、どれほど複雑でも自分のアプリで相対インポートを使えます。🤓

カスタムの tagsresponsesdependencies を追加

APIRouter に追加済みなので、各 path operation/items の prefix や tags=["items"] を付けていません。

しかし、特定の path operation に適用される 追加の tags や、その path operation 固有の追加の responses を加えることはできます:

app/routers/items.py
from fastapi import APIRouter, Depends, HTTPException

from ..dependencies import get_token_header

router = APIRouter(
    prefix="/items",
    tags=["items"],
    dependencies=[Depends(get_token_header)],
    responses={404: {"description": "Not found"}},
)


fake_items_db = {"plumbus": {"name": "Plumbus"}, "gun": {"name": "Portal Gun"}}


@router.get("/")
async def read_items():
    return fake_items_db


@router.get("/{item_id}")
async def read_item(item_id: str):
    if item_id not in fake_items_db:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"name": fake_items_db[item_id]["name"], "item_id": item_id}


@router.put(
    "/{item_id}",
    tags=["custom"],
    responses={403: {"description": "Operation forbidden"}},
)
async def update_item(item_id: str):
    if item_id != "plumbus":
        raise HTTPException(
            status_code=403, detail="You can only update the item: plumbus"
        )
    return {"item_id": item_id, "name": "The great Plumbus"}

豆知識

この最後の path operation は、["items", "custom"] のタグの組み合わせを持ちます。

またドキュメントには 404403 の両方のレスポンスが表示されます。

メインの FastAPI

次に、app/main.py のモジュールを見ていきます。

ここでクラス FastAPI をインポートして使用します。

これはすべてをまとめるアプリケーションのメインファイルになります。

そして大部分のロジックはそれぞれの専用モジュールに置かれるため、メインファイルはかなりシンプルになります。

FastAPI のインポート

通常どおり FastAPI クラスをインポートして作成します。

さらに、各 APIRouter の依存関係と組み合わされるグローバル依存関係も宣言できます:

app/main.py
from fastapi import Depends, FastAPI

from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users

app = FastAPI(dependencies=[Depends(get_query_token)])


app.include_router(users.router)
app.include_router(items.router)
app.include_router(
    admin.router,
    prefix="/admin",
    tags=["admin"],
    dependencies=[Depends(get_token_header)],
    responses={418: {"description": "I'm a teapot"}},
)


@app.get("/")
async def root():
    return {"message": "Hello Bigger Applications!"}
🤓 Other versions and variants
app/main.py
from fastapi import Depends, FastAPI

from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users

app = FastAPI(dependencies=[Depends(get_query_token)])


app.include_router(users.router)
app.include_router(items.router)
app.include_router(
    admin.router,
    prefix="/admin",
    tags=["admin"],
    dependencies=[Depends(get_token_header)],
    responses={418: {"description": "I'm a teapot"}},
)


@app.get("/")
async def root():
    return {"message": "Hello Bigger Applications!"}

Tip

Prefer to use the Annotated version if possible.

app/main.py
from fastapi import Depends, FastAPI

from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users

app = FastAPI(dependencies=[Depends(get_query_token)])


app.include_router(users.router)
app.include_router(items.router)
app.include_router(
    admin.router,
    prefix="/admin",
    tags=["admin"],
    dependencies=[Depends(get_token_header)],
    responses={418: {"description": "I'm a teapot"}},
)


@app.get("/")
async def root():
    return {"message": "Hello Bigger Applications!"}

APIRouter のインポート

次に、APIRouter を持つ他のサブモジュールをインポートします:

app/main.py
from fastapi import Depends, FastAPI

from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users

app = FastAPI(dependencies=[Depends(get_query_token)])


app.include_router(users.router)
app.include_router(items.router)
app.include_router(
    admin.router,
    prefix="/admin",
    tags=["admin"],
    dependencies=[Depends(get_token_header)],
    responses={418: {"description": "I'm a teapot"}},
)


@app.get("/")
async def root():
    return {"message": "Hello Bigger Applications!"}
🤓 Other versions and variants
app/main.py
from fastapi import Depends, FastAPI

from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users

app = FastAPI(dependencies=[Depends(get_query_token)])


app.include_router(users.router)
app.include_router(items.router)
app.include_router(
    admin.router,
    prefix="/admin",
    tags=["admin"],
    dependencies=[Depends(get_token_header)],
    responses={418: {"description": "I'm a teapot"}},
)


@app.get("/")
async def root():
    return {"message": "Hello Bigger Applications!"}

Tip

Prefer to use the Annotated version if possible.

app/main.py
from fastapi import Depends, FastAPI

from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users

app = FastAPI(dependencies=[Depends(get_query_token)])


app.include_router(users.router)
app.include_router(items.router)
app.include_router(
    admin.router,
    prefix="/admin",
    tags=["admin"],
    dependencies=[Depends(get_token_header)],
    responses={418: {"description": "I'm a teapot"}},
)


@app.get("/")
async def root():
    return {"message": "Hello Bigger Applications!"}

app/routers/users.pyapp/routers/items.py は同じ Python パッケージ app のサブモジュールなので、1つのドット . を使った「相対インポート」でインポートできます。

インポートの動作

次の部分:

from .routers import items, users

は次の意味です:

  • このモジュール(app/main.py)が存在する同じパッケージ(ディレクトリ app/)から開始し...
  • サブパッケージ routers(ディレクトリ app/routers/)を探し...
  • そこからサブモジュール items(ファイル app/routers/items.py)と users(ファイル app/routers/users.py)をインポートする...

モジュール items には変数 routeritems.router)があります。これは app/routers/items.py で作成した APIRouter オブジェクトと同じものです。

モジュール users についても同様です。

次のようにインポートすることもできます:

from app.routers import items, users

情報

最初のバージョンは「相対インポート」です:

from .routers import items, users

2つ目のバージョンは「絶対インポート」です:

from app.routers import items, users

Python のパッケージとモジュールについて詳しくは、公式の Python モジュールに関するドキュメントをご覧ください。

名前衝突の回避

サブモジュール items の変数 router だけをインポートするのではなく、サブモジュール自体を直接インポートしています。

これは、サブモジュール users にも router という変数があるためです。

もし次のように続けてインポートした場合:

from .routers.items import router
from .routers.users import router

usersrouteritems のものを上書きしてしまい、同時に両方を使えなくなります。

同じファイルで両方を使えるようにするため、サブモジュールを直接インポートします:

app/main.py
from fastapi import Depends, FastAPI

from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users

app = FastAPI(dependencies=[Depends(get_query_token)])


app.include_router(users.router)
app.include_router(items.router)
app.include_router(
    admin.router,
    prefix="/admin",
    tags=["admin"],
    dependencies=[Depends(get_token_header)],
    responses={418: {"description": "I'm a teapot"}},
)


@app.get("/")
async def root():
    return {"message": "Hello Bigger Applications!"}
🤓 Other versions and variants
app/main.py
from fastapi import Depends, FastAPI

from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users

app = FastAPI(dependencies=[Depends(get_query_token)])


app.include_router(users.router)
app.include_router(items.router)
app.include_router(
    admin.router,
    prefix="/admin",
    tags=["admin"],
    dependencies=[Depends(get_token_header)],
    responses={418: {"description": "I'm a teapot"}},
)


@app.get("/")
async def root():
    return {"message": "Hello Bigger Applications!"}

Tip

Prefer to use the Annotated version if possible.

app/main.py
from fastapi import Depends, FastAPI

from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users

app = FastAPI(dependencies=[Depends(get_query_token)])


app.include_router(users.router)
app.include_router(items.router)
app.include_router(
    admin.router,
    prefix="/admin",
    tags=["admin"],
    dependencies=[Depends(get_token_header)],
    responses={418: {"description": "I'm a teapot"}},
)


@app.get("/")
async def root():
    return {"message": "Hello Bigger Applications!"}

usersitemsAPIRouter を取り込む

では、サブモジュール usersitems から router を取り込みます:

app/main.py
from fastapi import Depends, FastAPI

from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users

app = FastAPI(dependencies=[Depends(get_query_token)])


app.include_router(users.router)
app.include_router(items.router)
app.include_router(
    admin.router,
    prefix="/admin",
    tags=["admin"],
    dependencies=[Depends(get_token_header)],
    responses={418: {"description": "I'm a teapot"}},
)


@app.get("/")
async def root():
    return {"message": "Hello Bigger Applications!"}
🤓 Other versions and variants
app/main.py
from fastapi import Depends, FastAPI

from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users

app = FastAPI(dependencies=[Depends(get_query_token)])


app.include_router(users.router)
app.include_router(items.router)
app.include_router(
    admin.router,
    prefix="/admin",
    tags=["admin"],
    dependencies=[Depends(get_token_header)],
    responses={418: {"description": "I'm a teapot"}},
)


@app.get("/")
async def root():
    return {"message": "Hello Bigger Applications!"}

Tip

Prefer to use the Annotated version if possible.

app/main.py
from fastapi import Depends, FastAPI

from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users

app = FastAPI(dependencies=[Depends(get_query_token)])


app.include_router(users.router)
app.include_router(items.router)
app.include_router(
    admin.router,
    prefix="/admin",
    tags=["admin"],
    dependencies=[Depends(get_token_header)],
    responses={418: {"description": "I'm a teapot"}},
)


@app.get("/")
async def root():
    return {"message": "Hello Bigger Applications!"}

情報

users.router は、ファイル app/routers/users.py 内の APIRouter を含みます。

items.router は、ファイル app/routers/items.py 内の APIRouter を含みます。

app.include_router() を使って、各 APIRouter をメインの FastAPI アプリケーションに追加できます。

そのルーターのすべてのルートがアプリに含まれます。

技術詳細

実際には、APIRouter で宣言された各 path operation ごとに内部的に path operation が作成されます。

つまり裏側では、すべてが同じ単一のアプリであるかのように動作します。

確認

ルーターを取り込んでもパフォーマンスを心配する必要はありません。

これは起動時にマイクロ秒で行われます。

したがってパフォーマンスには影響しません。⚡

カスタムの prefixtagsresponsesdependencies 付きで APIRouter を取り込む

あなたの組織から app/internal/admin.py ファイルが提供されたとしましょう。

そこには、組織が複数プロジェクトで共有している管理用の path operations を持つ APIRouter が含まれています。

この例ではとてもシンプルですが、組織内の他プロジェクトと共有しているため、APIRouter 自体を直接変更して prefixdependenciestags などを追加できないとします:

app/internal/admin.py
from fastapi import APIRouter

router = APIRouter()


@router.post("/")
async def update_admin():
    return {"message": "Admin getting schwifty"}

それでも、APIRouter を取り込む際にカスタムの prefix を設定してすべての path operations/admin で始めたい、既存の dependencies で保護したい、さらに tagsresponses も含めたいとします。

元の APIRouter を変更することなく、app.include_router() にこれらのパラメータを渡すことで宣言できます:

app/main.py
from fastapi import Depends, FastAPI

from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users

app = FastAPI(dependencies=[Depends(get_query_token)])


app.include_router(users.router)
app.include_router(items.router)
app.include_router(
    admin.router,
    prefix="/admin",
    tags=["admin"],
    dependencies=[Depends(get_token_header)],
    responses={418: {"description": "I'm a teapot"}},
)


@app.get("/")
async def root():
    return {"message": "Hello Bigger Applications!"}
🤓 Other versions and variants
app/main.py
from fastapi import Depends, FastAPI

from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users

app = FastAPI(dependencies=[Depends(get_query_token)])


app.include_router(users.router)
app.include_router(items.router)
app.include_router(
    admin.router,
    prefix="/admin",
    tags=["admin"],
    dependencies=[Depends(get_token_header)],
    responses={418: {"description": "I'm a teapot"}},
)


@app.get("/")
async def root():
    return {"message": "Hello Bigger Applications!"}

Tip

Prefer to use the Annotated version if possible.

app/main.py
from fastapi import Depends, FastAPI

from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users

app = FastAPI(dependencies=[Depends(get_query_token)])


app.include_router(users.router)
app.include_router(items.router)
app.include_router(
    admin.router,
    prefix="/admin",
    tags=["admin"],
    dependencies=[Depends(get_token_header)],
    responses={418: {"description": "I'm a teapot"}},
)


@app.get("/")
async def root():
    return {"message": "Hello Bigger Applications!"}

このようにすると、元の APIRouter は未変更のままなので、同じ app/internal/admin.py ファイルを組織内の他プロジェクトとも引き続き共有できます。

結果として、このアプリ内では admin モジュールの各 path operation が次のようになります:

  • prefix は /admin
  • タグは admin
  • 依存関係は get_token_header
  • レスポンスは 418 🍵

ただし、これはこのアプリ内のその APIRouter にのみ影響し、それを使用する他のコードには影響しません。

例えば、他のプロジェクトでは同じ APIRouter を別の認証方式で使うこともできます。

path operation を追加

FastAPI アプリに path operations を直接追加することもできます。

ここでは(できることを示すためだけに)追加します 🤷:

app/main.py
from fastapi import Depends, FastAPI

from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users

app = FastAPI(dependencies=[Depends(get_query_token)])


app.include_router(users.router)
app.include_router(items.router)
app.include_router(
    admin.router,
    prefix="/admin",
    tags=["admin"],
    dependencies=[Depends(get_token_header)],
    responses={418: {"description": "I'm a teapot"}},
)


@app.get("/")
async def root():
    return {"message": "Hello Bigger Applications!"}
🤓 Other versions and variants
app/main.py
from fastapi import Depends, FastAPI

from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users

app = FastAPI(dependencies=[Depends(get_query_token)])


app.include_router(users.router)
app.include_router(items.router)
app.include_router(
    admin.router,
    prefix="/admin",
    tags=["admin"],
    dependencies=[Depends(get_token_header)],
    responses={418: {"description": "I'm a teapot"}},
)


@app.get("/")
async def root():
    return {"message": "Hello Bigger Applications!"}

Tip

Prefer to use the Annotated version if possible.

app/main.py
from fastapi import Depends, FastAPI

from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users

app = FastAPI(dependencies=[Depends(get_query_token)])


app.include_router(users.router)
app.include_router(items.router)
app.include_router(
    admin.router,
    prefix="/admin",
    tags=["admin"],
    dependencies=[Depends(get_token_header)],
    responses={418: {"description": "I'm a teapot"}},
)


@app.get("/")
async def root():
    return {"message": "Hello Bigger Applications!"}

そして、app.include_router() で追加したほかの path operations と一緒に正しく動作します。

非常に技術的な詳細

注記: これは非常に技術的な詳細で、読み飛ばして構いません


APIRouter は「マウント」されておらず、アプリケーションの他部分から分離されていません。

これは、それらの path operations を OpenAPI スキーマやユーザーインターフェースに含めたいからです。

完全に分離して独立に「マウント」できないため、path operations は直接取り込まれるのではなく「クローン(再作成)」されます。

自動APIドキュメントの確認

アプリを実行します:

$ fastapi dev app/main.py

<span style="color: green;">INFO</span>:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

そして http://127.0.0.1:8000/docs を開きます。

すべてのサブモジュール由来のパスを含む自動 API ドキュメントが表示され、正しいパス(および prefix)と正しいタグが使われているのが分かります:

同じルーターを異なる prefix で複数回取り込む

同じルーターに対して、異なる prefix で .include_router() を複数回使うこともできます。

例えば、同じ API を /api/v1/api/latest のように異なる prefix で公開する場合に役立ちます。

高度な使い方なので不要かもしれませんが、必要な場合に備えて用意されています。

APIRouter を別の APIRouter に取り込む

APIRouterFastAPI アプリケーションに取り込めるのと同じように、APIRouter を別の APIRouter に取り込むこともできます:

router.include_router(other_router)

routerFastAPI アプリに取り込む前にこれを実行して、other_routerpath operations も含まれるようにしてください。