より大きなアプリケーション - 複数のファイル¶
アプリケーションやWeb APIを構築する場合、すべてを単一のファイルにまとめることはめったにありません。
FastAPI は、すべての柔軟性を保ちながらアプリケーションを構造化するための便利なツールを提供します。
情報
Flask出身の方なら、これはFlaskのBlueprintsに相当します。
ファイル構造の例¶
次のようなファイル構造があるとします。
.
├── app
│ ├── __init__.py
│ ├── main.py
│ ├── dependencies.py
│ └── routers
│ │ ├── __init__.py
│ │ ├── items.py
│ │ └── users.py
│ └── internal
│ ├── __init__.py
│ └── admin.py
ヒント
各ディレクトリまたはサブディレクトリに __init__.py ファイルがいくつかあります。
これにより、あるファイルから別のファイルにコードをインポートできます。
例えば、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 のサブモジュールであるとします。
ユーザーに関連する パス操作 を他のコードから分離して、整理しておきたいと考えています。
しかし、それは依然として同じ FastAPI アプリケーション/Web API の一部です(同じ「Pythonパッケージ」の一部です)。
APIRouter を使用して、そのモジュールの パス操作 を作成できます。
APIRouter のインポート¶
FastAPI クラスと同じようにインポートして「インスタンス」を作成します。
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 クラスを使用するのと同じ方法で使用します。
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」クラスと考えることができます。
同じオプションがすべてサポートされています。
同じ parameters、responses、dependencies、tags などすべて。
ヒント
この例では、変数は router と呼ばれていますが、好きなように名前を付けることができます。
この APIRouter をメインの FastAPI アプリに含める予定ですが、まず依存関係と別の APIRouter を確認しましょう。
依存性注入¶
アプリケーションのいくつかの場所で使用される依存関係が必要になることがわかります。
そこで、それらを独自の dependencies モジュール (app/dependencies.py) に配置します。
ここでは、カスタム X-Token ヘッダーを読み取るための簡単な依存関係を使用します。
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")
from fastapi import Header, HTTPException
from typing_extensions import Annotated
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")
ヒント
可能であれば`Annotated`バージョンを使用することをお勧めします。
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")
APIRouter を含む別のモジュール¶
アプリケーションの「アイテム」を処理するためのエンドポイントが app/routers/items.py のモジュールにもあるとします。
次の パス操作 があります。
/items//items/{item_id}
app/routers/users.py と同じ構造です。
しかし、もっと賢く、コードを少し簡素化したいと考えています。
このモジュールのすべての パス操作 が同じであることを知っています。
- パス
prefix:/items。 tags: (1つのタグ:items)。- 追加の
responses。 dependencies: これらはすべて、作成したX-Token依存関係を必要とします。
したがって、これらすべてを各 パス操作 に追加する代わりに、APIRouter に追加できます。
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"}
各 パス操作 のパスは次のように / で始まる必要があるため、
@router.get("/{item_id}")
async def read_item(item_id: str):
...
...プレフィックスには最後の / を含めないでください。
したがって、この場合のプレフィックスは /items です。
このルーターに含まれるすべての パス操作 に適用される tags のリストと追加の responses も追加できます。
そして、ルーター内のすべての パス操作 に追加され、それらへの各リクエストに対して実行/解決される dependencies のリストを追加できます。
ヒント
パス操作デコレータ 内の依存関係 と同様に、値は パス操作関数 に渡されないことに注意してください。
最終結果として、アイテムパスは次のようになります。
/items//items/{item_id}
...意図したとおり。
- それらは、単一の文字列
"items"を含むタグのリストでマークされます。- これらの「タグ」は、自動インタラクティブドキュメントシステム (OpenAPIを使用) に特に役立ちます。
- それらはすべて、事前定義された
responsesを含みます。 - これらのすべての パス操作 は、その前に
dependenciesのリストが評価/実行されます。- 特定の パス操作 で依存関係も宣言した場合、それらも実行されます。
- ルーターの依存関係が最初に実行され、次にデコレータ内の
dependenciesが実行され、次に通常のパラメーターの依存関係が実行されます。 scopesを持つSecurity依存関係 も追加できます。
ヒント
APIRouter に dependencies を持たせることは、たとえば、パス操作 のグループ全体に認証を要求するために使用できます。依存関係がそれぞれに個別に追加されていなくても。
確認
prefix、tags、responses、dependencies パラメーターは(他の多くのケースと同様に)コードの重複を避けるのに役立つ FastAPI の機能にすぎません。
依存関係のインポート¶
このコードはモジュール app.routers.items、ファイル app/routers/items.py にあります。
そして、モジュール app.dependencies、ファイル app/dependencies.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"}
相対インポートの仕組み¶
ヒント
インポートの仕組みを完璧に知っている場合は、以下の次のセクションに進んでください。
次のような単一のドット . は
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 ファイルなどを持つことになります。しかし、私たちにはそれがありません。したがって、この例ではエラーが発生します。🚨
しかし、これで仕組みがわかったので、どんなに複雑なアプリでも相対インポートを使用できます。🤓
カスタム tags、responses、dependencies の追加¶
APIRouter に追加したため、各 パス操作 にプレフィックス /items や tags=["items"] は追加していません。
しかし、特定の パス操作 に適用される さらに 多くの tags や、その パス操作 に固有の追加の responses を追加することもできます。
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"}
ヒント
この最後のパス操作には、["items", "custom"] のタグの組み合わせが適用されます。
そして、ドキュメントには 404 と 403 の両方のレスポンスも含まれます。
メインの FastAPI¶
次に、app/main.py のモジュールを見てみましょう。
ここで FastAPI クラスをインポートして使用します。
これは、アプリケーション内のすべてのものをまとめるメインファイルになります。
そして、ほとんどのロジックは独自の特定のモジュールに存在するようになるため、メインファイルは非常にシンプルになります。
FastAPI のインポート¶
通常通り、FastAPI クラスをインポートして作成します。
そして、各 APIRouter の依存関係と組み合わされるグローバル依存関係を宣言することもできます。
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 を持つ他のサブモジュールをインポートします。
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.py と app/routers/items.py は同じ Python パッケージ app の一部であるサブモジュールなので、「相対インポート」を使用してそれらをインポートするために単一のドット . を使用できます。
インポートの仕組み¶
セクション
from .routers import items, users
という意味になります。
- このモジュール (ファイル
app/main.py) が存在するのと同じパッケージ (ディレクトリapp/) から開始して... - サブパッケージ
routers(ディレクトリapp/routers/) を探し... - そしてそこから、サブモジュール
items(ファイルapp/routers/items.py) とusers(ファイルapp/routers/users.py) をインポートします。
モジュール items には変数 router (items.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 だけをインポートするのではなく、サブモジュール items を直接インポートしています。
これは、サブモジュール users にも router という名前の別の変数があるためです。
次のように、次々とインポートした場合、
from .routers.items import router
from .routers.users import router
users の router は items のものを上書きし、同時に使用することはできません。
したがって、同じファイル内で両方を使用できるように、サブモジュールを直接インポートします。
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 と items の APIRouter を含める¶
次に、サブモジュール users と items から router を含めてみましょう。
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 で宣言された各 パス操作 に対して、内部的に パス操作 が作成されます。
したがって、舞台裏では、すべてが同じ単一のアプリであるかのように機能します。
確認
ルーターを含めるときにパフォーマンスを心配する必要はありません。
これはマイクロ秒単位で実行され、起動時にのみ発生します。
したがって、パフォーマンスには影響しません。⚡
カスタム prefix、tags、responses、および dependencies を持つ APIRouter を含める¶
さて、あなたの組織が app/internal/admin.py ファイルをあなたに提供したとしましょう。
これには、組織が複数のプロジェクトで共有する管理者用の パス操作 を持つ APIRouter が含まれています。
この例では非常にシンプルですが、組織内の他のプロジェクトと共有されているため、直接 APIRouter に prefix、dependencies、tags などを追加して変更することはできないとしましょう。
from fastapi import APIRouter
router = APIRouter()
@router.post("/")
async def update_admin():
return {"message": "Admin getting schwifty"}
しかし、APIRouter を含める際にカスタムの prefix を設定して、そのすべての パス操作 が /admin で始まるようにしたいと考えています。このプロジェクトですでに持っている dependencies で保護し、tags と responses を含めたいと考えています。
app.include_router() にこれらのパラメーターを渡すことで、元の APIRouter を変更することなく、これらすべてを宣言できます。
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 モジュールの各 パス操作 が次のようになります。
- プレフィックス
/admin。 - タグ
admin。 - 依存関係
get_token_header。 - レスポンス
418。🍵
ただし、それは私たちのアプリのその APIRouter にのみ影響し、それを使用する他のコードには影響しません。
したがって、たとえば、他のプロジェクトは同じ APIRouter を異なる認証方法で使用できます。
パス操作 を含める¶
FastAPI アプリに直接 パス操作 を追加することもできます。
ここでは、できることを示すためにやっています 🤷
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() で追加された他のすべての パス操作 と一緒に、正しく機能します。
非常に技術的な詳細
注: これは非常に技術的な詳細であり、おそらく スキップしても構いません。
APIRouter は「マウント」されておらず、アプリケーションの残りの部分から分離されていません。
これは、それらの パス操作 を OpenAPI スキーマとユーザーインターフェイスに含めたいからです。
それらを分離して残りの部分から独立して「マウント」することはできないため、パス操作 は直接含まれるのではなく、「クローン作成」(再作成)されます。
自動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 で複数回含める¶
また、異なるプレフィックスを使用して、同じ ルーターで .include_router() を複数回使用することもできます。
これは、たとえば、/api/v1 や /api/latest など、異なるプレフィックスの下で同じ API を公開するのに役立ちます。
これは高度な使い方であり、実際には必要ないかもしれませんが、必要な場合に備えて存在します。
別の APIRouter に APIRouter を含める¶
FastAPI アプリケーションに APIRouter を含めるのと同じ方法で、別の APIRouter に APIRouter を含めることができます。
router.include_router(other_router)
other_router からの パス操作 も含まれるように、router を FastAPI アプリに含める前にこれを行うようにしてください。