
これまでmain.pyにまとめて実装していたAPIを、APIRouterを使って機能ごとに分割します。
また、Dependsによる依存性注入の仕組みを学び、複数のAPIで利用する共通処理を整理します。
最後にPydanticモデルなども含めてファイル構成を見直し、実務を意識したFastAPIアプリの構成へ整理していきます。

それではSTEP5を始めましょう!
今回学ぶこと
今回のステップで学ぶ内容を一覧で出してもらいました。

STEP5では以下の内容を学習します。
- APIRouterの役割と仕組みを理解する
- APIをRouterファイルへ分割する
- main.pyからRouterを登録して動作確認する
- Dependsと依存性注入の仕組みを理解する
- Dependsを使った共通処理を実装する
- schemas・routers・dependenciesを分けて構成を整理する
- 自力課題
学習開始
それでは学習をはじめます。
APIRouterの役割と仕組みを理解する

STEP 4までは、すべてのAPIをmain.pyへ直接書いていました。APIが数個ならこれでも問題ありません。
ただ、実務のアプリになると、
商品API
ユーザーAPI
注文API
認証API
...
というようにエンドポイントが増えていきます。全部main.pyに書き続けると、main.pyが巨大になってしまいます。
そこでFastAPIでは、APIRouterを使って関連するAPIをグループ化し、別ファイルへ分割できます。イメージとしては以下のような構成です。
main.py
│
├── products.py
│ ├── POST /products
│ ├── GET /products/{id}
│ ├── PUT /products/{id}
│ └── DELETE /products/{id}
│
└── users.py
├── POST /users
└── GET /users/{id}
appではなくrouterにAPIを登録する

これまで使ってきたのは、@app.get("/products")でした。
Routerファイルでは、まずAPIRouterを作成します。
from fastapi import APIRouter
router = APIRouter()
そして、以下のようにAPIを登録します。
@router.get("/products")
def get_products():
...

ただし、このrouterを作っただけではFastAPIアプリからは認識されません。
全体像としては次のようになります。
products.py
APIRouter
↓
商品APIを登録
↓
router
│
│ include_router()
↓
main.py
↓
FastAPI
DRFでいうと、完全に同じ仕組みではありませんが、views.py、urls.pyなどへ処理やURL定義を分けて、プロジェクト全体に取り込んでいく感覚に近いです。
FastAPIではAPIRouter自体が、「このグループには、こういうエンドポイントがあります」という情報を持ち、それをFastAPI本体へinclude_router()で登録します。
ここでまず押さえたいのは、APIRouterは、関連するAPIをグループ化して別ファイルへ分割し、FastAPIアプリへ組み込むための仕組みという点です。
APIをRouterファイルへ分割する

今回はまず、STEP 4で作った商品APIだけをmain.pyからRouterファイルへ移動します。
app配下にroutersフォルダを作成し、その中にproducts.pyを作ります。
app/
├── main.py
└── routers/
└── products.py
今回のポイントは、いきなり構成を全部整理するのではなく、これまでmain.pyに書いていた商品APIを、APIRouterへ移すところだけです。
app/routers/products.pyに次のように記述してください。
記述するコード↓
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel, Field
router = APIRouter()
class ProductCreate(BaseModel):
name: str
price: int = Field(gt=0)
description: str | None = None
class ProductUpdate(BaseModel):
name: str
price: int = Field(gt=0)
description: str | None = None
products = {
1: {
"name": "ノートPC",
"price": 120000,
"description": "開発用PC",
},
2: {
"name": "マウス",
"price": 3000,
"description": None,
},
}
ここまでは、STEP 4でmain.pyに書いていた商品API用のデータやPydanticモデルを移しただけです。
そして、商品に関するCRUDのデコレータを、
@app.post(...)
@app.get(...)
@app.put(...)
@app.delete(...)
から、
@router.post(...)
@router.get(...)
@router.put(...)
@router.delete(...)
へ変更して、商品APIの各関数もすべてproducts.pyへ移してください。
ここではまだmain.pyからRouterを登録しないので、この時点では商品APIが動かなくても正常です。
修正したproducts.py↓
from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel, Field
router = APIRouter()
class ProductCreate(BaseModel):
name: str
price: int = Field(gt=0)
description: str | None = None
class ProductUpdate(BaseModel):
name: str
price: int = Field(gt=0)
description: str | None = None
products = {
1: {
"name": "ノートPC",
"price": 120000,
"description": "開発用PC",
},
2: {
"name": "マウス",
"price": 3000,
"description": None,
},
}
@router.post(
"/products",
status_code=status.HTTP_201_CREATED,
)
def create_product(product: ProductCreate):
# 現在の商品IDの最大値から、新しい商品IDを作成する
new_product_id = max(products.keys(), default=0) + 1
# Pydanticモデルを辞書に変換する
product_data = product.model_dump()
# 新しい商品を保存する
products[new_product_id] = product_data
# 作成された商品IDと商品データを返す
return {
"id": new_product_id,
**product_data,
}
@router.get(
"/products/{product_id}",
responses={
404: {
"detail": "Product not found",
}
}
)
def get_product(product_id: int):
# 指定された商品IDの商品を取得する
product = products.get(product_id)
# 商品が存在しない場合は404エラーを返す
if product is None:
raise HTTPException(
status_code=404,
detail="Product not found",
)
return product
@router.put("/products/{product_id}")
def update_product(product_id: int, product: ProductUpdate):
# 商品が存在しない場合は404エラーを返す
if product_id not in products:
raise HTTPException(
status_code=404,
detail="Product not found",
)
# 指定された商品IDのデータを更新する
products[product_id] = product.model_dump()
return products[product_id]
@router.delete("/products/{product_id}")
def delete_product(product_id: int):
# 商品が存在しない場合は404エラーを返す
if product_id not in products:
raise HTTPException(
status_code=404,
detail="Product not found",
)
# 指定された商品IDのデータを削除する
deleted_product = products.pop(product_id)
return deleted_product
このコードをChatGPT先生に見せました。

問題ありません。今回の目的である「商品APIをmain.pyからAPIRouterへ移す」ことは正しくできています。
1点だけ、Routerとは直接関係ありませんが、以前からあるこの部分は、
responses={
404: {
"detail": "Product not found",
}
}
OpenAPI上のレスポンス説明として書くなら、後で
responses={
404: {
"description": "Product not found",
}
}
のようにする方が適切です。ここは今回のRouter学習とは別なので、今すぐ深掘りする必要はありません。
1点指摘がありました。STEP4でChatGPT先生が提示したコードをそのまま貼り付けていた部分だったのですが、descriptionの方が適切だそうです。

このresponsesは、Swagger UI / OpenAPIドキュメントに「404の場合はこういうレスポンスです」と説明を追加するための設定です。FastAPI公式ドキュメントでも、追加レスポンスの説明にはdescriptionを使っています。
一方、実際に404エラーを返す以下は、detailで正しいです。HTTPExceptionのdetailは、クライアントへ返すJSONレスポンスのdetailフィールドに入る値だからです。
raise HTTPException(
status_code=404,
detail="Product not found",
)
つまり整理すると、以下のようになります。
responses={404: {"description": "..."}}
↑
APIドキュメント上の説明
HTTPException(detail="...")
↑
実際のエラーレスポンス
とのことでしたので、products.pyを修正しました。
main.pyからRouterを登録して動作確認する

現在のproducts.pyには商品APIがありますが、この時点では、以下のところまでしかできていません。
products.py
↓
APIRouterに商品APIを登録
FastAPI本体であるappは、まだこのrouterの存在を知りません。
そこでmain.pyから登録します。
main.pyのimport部分へ追加してください。
from app.routers.products import router as products_router
ここでは、routerという名前のままimportすると、今後Routerが増えたときに何のRouterなのか分かりにくくなるため、products_routerという名前にしています。
そして、app = FastAPI()の後に次のコードを追加します。
app.include_router(products_router)
何が起きているのか

products.pyでは、router = APIRouter()に以下を登録しました。
POST /products
GET /products/{product_id}
PUT /products/{product_id}
DELETE /products/{product_id}
そのrouterを、app.include_router(products_router)でFastAPI本体へ組み込んでいます。
イメージすると、以下のような関係です。
products.py
┌─────────────────────────┐
│ products_router │
│ │
│ POST /products │
│ GET /products/{id} │
│ PUT /products/{id} │
│ DELETE /products/{id} │
└───────────┬─────────────┘
│
│ include_router()
▼
main.py
┌─────────────────────────┐
│ FastAPI app │
└─────────────────────────┘
動作確認

以下の4つがこれまでと同じようにSwagger UIへ表示されているか確認します。
POST /products
GET /products/{product_id}
PUT /products/{product_id}
DELETE /products/{product_id}
さらに、GET /products/1を実行して以下のレスポンスが返ってくればOKです。
{
"name": "ノートPC",
"price": 120000,
"description": "開発用PC"
}
各エンドポイントがSwagger UIに表示されていることを確認↓

GET /products/1の実行結果↓

Dependsと依存性注入の仕組みを理解する

ここからSTEP 5のもう一つの中心であるDependsに入ります。
まずコードを書く前に、「何のために使うのか」を押さえましょう。
APIには共通処理が増えてくる

たとえば今後、複数のAPIで次のような処理が必要になることがあります。
- ログインユーザーを取得する
- APIキーをチェックする
- DBセッションを取得する
- 共通のクエリパラメータを処理する
仮に、複数のAPIすべてで認証チェックが必要だとします。
これを普通に書くと、以下のようになり同じ処理が何度も登場します。
def get_product():
# 認証チェック
...
# 商品取得処理
...
def create_product():
# 認証チェック
...
# 商品登録処理
...
def update_product():
# 認証チェック
...
# 商品更新処理
...
共通処理を関数にすれば、以下のように書けます。
def check_auth():
...
def get_product():
check_auth()
...
def create_product():
check_auth()
...
もちろんPythonとしてはこれでも動きます。
FastAPIでは、このような「APIの処理を実行するために必要となる処理や値」をDependsを使って宣言できます。
Dependsの基本形

たとえば、次のような共通処理があるとします。
from fastapi import Depends
def get_common_value():
return "共通の値"
Dependsを使ってAPI側では次のように書けます。
@router.get("/example")
def example(
common_value: str = Depends(get_common_value),
):
return {"value": common_value}
処理順は、以下のようになります。
リクエスト
↓
FastAPI
↓
Depends(get_common_value)
↓
get_common_value() を実行
↓
戻り値を common_value に渡す
↓
example() を実行
なぜ「依存性注入」と呼ぶのか

少し名前が難しいのですが、ここは言葉を分解すると分かりやすいです。
example()は、common_valueという値を必要としています。つまりexample()はcommon_valueに依存していると考えます。
しかし、example()自身が、common_value = get_common_value()と取得するのではありません。
代わりに、Depends(get_common_value)と宣言しておくと、FastAPIが必要な値を用意して関数へ渡してくれます。
この「外側から必要なものを渡す」という考え方が依存性注入(Dependency Injection / DI)です。
<自分で取得する>
API → get_common_value()を直接呼ぶ
<Dependsを使う>
API ← FastAPIが必要な値を渡す
という違いがあります。
Depends()は単なる関数呼び出しではない

ここは重要です。
Depends(get_common_value)とget_common_value()は意味が違います。
get_common_value()なら、その場所で普通に関数を呼び出しています。
一方、Depends(get_common_value)は、「このAPIはget_common_valueに依存しています。この処理はFastAPIに任せます」という宣言です。
FastAPIがその依存関係を解決してから、API関数を実行します。
実務では何に使う?

FastAPIで特によく見るのは、Depends(get_db)のようなDB接続・DBセッションの取得や、Depends(get_current_user)のような認証済みユーザーの取得です。
STEP 6でSQLAlchemyを扱うようになると、DependsとDBセッションを組み合わせる形も出てきます。
なので今回のSTEP 5では、まず簡単な共通処理でDependsそのものの仕組みを理解するところまでやります。
DRF経験と結び付けるなら、認証や権限チェックなど「各Viewが必要とする共通機能」をフレームワーク側の仕組みに乗せる、という発想には近い部分があります。ただしFastAPIのDependsは認証専用ではなく、任意の依存処理を宣言できる汎用的な仕組みです。
ここで押さえておきたいのは、Dependsは、APIが必要とする共通処理や値をFastAPIに解決してもらい、API関数へ渡すための仕組みという点です。
Dependsを使った共通処理を実装する

今回やりたいことは、商品一覧APIで使う以下のクエリパラメータの取得処理を、API本体から切り離して共通処理として扱うことです。
limit
offset
今回の完成形は次の形です。
完成形のコード↓
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, status
router = APIRouter()
def get_pagination(limit: int = 10, offset: int = 0):
# 一覧取得で使用するページング条件をまとめて返す
return {
"limit": limit,
"offset": offset,
}
@router.get("/products")
def get_products(
pagination: Annotated[dict, Depends(get_pagination)],
):
# ページング条件を取得する
limit = pagination["limit"]
offset = pagination["offset"]
# 商品IDを含めた一覧データを作成する
product_list = [
{
"id": product_id,
**product,
}
for product_id, product in products.items()
]
# offsetの位置からlimit件の商品を返す
return product_list[offset : offset + limit]
① get_pagination()は何をしているのか

まずここです。これは普通のPython関数です。
def get_pagination(limit: int = 10, offset: int = 0):
return {
"limit": limit,
"offset": offset,
}
ただしFastAPIから見ると、以下はパスパラメータではないのでクエリパラメータとして扱われます。
limit: int = 10
offset: int = 0
つまり、GET /products?limit=5&offset=10というリクエストが来た場合、FastAPIはこの関数に対して、
get_pagination(
limit=5,
offset=10,
)
相当の値を渡してくれます。そしてこの関数は以下を返します。
{
"limit": 5,
"offset": 10,
}
② Depends(get_pagination)は何を意味しているのか

商品一覧APIではこう書いています。
pagination: Annotated[dict, Depends(get_pagination)]
ここを分解すると、次のようになります。
Annotated[
dict,
Depends(get_pagination),
]
まず、dictはpaginationには辞書型の値が入るというPythonの型情報です。
そして、Depends(get_pagination)はFastAPIに対して、paginationの値を用意するときは、get_paginationを依存処理として使ってくださいと伝えています。
つまり、get_products()自身が、get_pagination()を直接呼んでいるわけではありません。FastAPIが代わりに呼び出します。
③ 実際のリクエスト時の処理順

GET /products?limit=1&offset=1のリクエストが来た時の処理の流れはこうです。
GET /products?limit=1&offset=1
↓
FastAPI
↓
Depends(get_pagination) を確認
↓
get_pagination(limit, offset) の引数をリクエストから解決
↓
get_pagination() を実行
↓
戻り値を pagination に渡す
↓
get_products() を実行
④ get_products()側は依存処理の結果だけを使う

get_products()では、次のようにしています。
limit = pagination["limit"]
offset = pagination["offset"]
この時点ではもう、「クエリパラメータをどこから取得するか」、「デフォルト値はいくつか」といったことをget_products()側で考える必要がありません。
get_products()は、ページング条件はpaginationに入ってくるという前提で、自分の商品一覧取得処理だけに集中できます。
これがDependsを使うメリットの一つです。
⑤ なぜこれが「共通処理」になるのか

今回はget_products()からしか使っていません。でも今後、@router.get("/users")や@router.get("/orders")でも同じページング条件を使いたくなった場合、それぞれで、
limit: int = 10
offset: int = 0
を書く代わりに、
pagination: Annotated[dict, Depends(get_pagination)]
と書けば、同じ処理を再利用できます。
つまり、処理の役割を次のように分けられています。
ページング条件を取得する責務:get_pagination()
商品一覧を取得する責務:get_products()
⑥ Annotatedを使う理由

以下の書き方でもFastAPIとしては動きます。
pagination: dict = Depends(get_pagination)
ただ、
pagination: Annotated[dict, Depends(get_pagination)]
とすることで、以下を1つの型アノテーションとしてまとめられます。
dict:Pythonとしての型情報Depends(get_pagination):FastAPIに対する依存関係の追加情報
最初の書き方では、今回使っているRuffのB008警告が発生してしまいますが、Annotatedを使用することで警告を回避できます。
⑦一番理解してほしい部分

今回の項目5で一番理解してほしいのは、次の部分です。
Depends(get_pagination)と書くと、get_products()がget_pagination()を直接呼ぶのではなく、FastAPIが依存関係を解決して、戻り値をpaginationへ渡してくれる。
そして、
get_pagination()の引数もFastAPIが解析するため、limitとoffsetがクエリパラメータとして扱われる。
の2点です。
動作確認

Swagger UIでGET /productsが追加されていることを確認し、次の2パターンを試してみてください。
<パターン1>
limit = 1
offset = 0
<パターン2>
limit = 1
offset = 1
パターン1の実行結果↓

パターン2の実行結果↓


問題ありません。
リクエスト
↓
get_pagination()
↓
pagination
↓
get_products()
というDependsの依存関係が想定どおり動いています。
schemas・routers・dependenciesを分けて構成を整理する

ここでは、今のproducts.pyに集まっているものを役割ごとに分離します。
現在はざっくり、以下のようになっています。
products.py
├── ProductCreate
├── ProductUpdate
├── products
├── get_pagination()
└── 商品API
これを今回は、次の構成に整理します。
app/
├── main.py
├── dependencies.py
├── routers/
│ └── products.py
└── schemas/
└── products.py
それぞれの役割はこう考えます。
| ファイル | 役割 |
|---|---|
main.py | FastAPIアプリ本体とRouterの登録 |
routers/products.py | 商品APIのエンドポイント |
schemas/products.py | 商品APIで使うPydanticモデル |
dependencies.py | 複数のAPIから利用できる依存処理 |
小規模なAPIなら同じファイルでも問題ありませんが、実務で規模が大きくなるなら、Pydanticモデルをschemasとして分離する構成はよく使われます。
こうすると、次のように責務が分かりやすくなります。
APIの入口 → routers
データの形 → schemas
共通の依存処理 → dependencies
schemasを分離する

一気に全部変更すると何を移したのか分かりづらくなるので、まずPydanticモデルだけ移しましょう。
app/schemas/products.pyを作成してください。
from pydantic import BaseModel, Field
class ProductCreate(BaseModel):
name: str
price: int = Field(gt=0)
description: str | None = None
class ProductUpdate(BaseModel):
name: str
price: int = Field(gt=0)
description: str | None = None
そして、現在のapp/routers/products.pyから、以下を削除します。
class ProductCreate(BaseModel):
...
class ProductUpdate(BaseModel):
...
代わりに以下をimportします。
from app.schemas.products import ProductCreate, ProductUpdate
その結果、routers/products.pyではもう、以下は不要になるので削除します。
from pydantic import BaseModel, Field
つまりRouter側から見ると、「リクエストのデータ構造についてはschemas側から借りてくる」という状態になります。
dependencies.pyへ分離する

次は、現在routers/products.pyに残っているこの処理を移します。
def get_pagination(limit: int = 10, offset: int = 0):
# 一覧取得で使用するページング条件をまとめて返す
return {
"limit": limit,
"offset": offset,
}
これは商品固有の処理というより、一覧取得で共通利用できるページング条件を作る処理なので、Routerから切り離してdependencies.pyへ置いてみます。
現在の構成にapp/dependencies.pyを追加してください。
app/
├── main.py
├── dependencies.py ← 追加
├── routers/
│ └── products.py
└── schemas/
└── products.py
dependencies.pyには、先ほどの関数をそのまま移します。
そしてrouters/products.pyからget_pagination()を削除して、代わりにimportします。
from app.dependencies import get_pagination
つまりRouter側では、次のような形になります。
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, status
from app.dependencies import get_pagination
from app.schemas.products import ProductCreate, ProductUpdate
Dependsの部分は変更しません。ここがポイントで、依存処理を別ファイルへ移しても、Dependsの仕組み自体は何も変わりません。
routers/products.py
│
│ Depends(get_pagination)
▼
dependencies.py
│
│ get_pagination()
▼
limit / offset
という関係になります。これで、
APIの入口 → routers
データ構造 → schemas
共通の依存処理 → dependencies
まで責務を分離できます。今回の整理によって、
routers
→ APIのエンドポイント・処理
schemas
→ リクエストなどのデータ構造
dependencies
→ APIから利用する共通の依存処理
main.py
→ FastAPIアプリの作成・Routerの登録
という構成になりました。
これは今回のSTEPでかなり重要なポイントです。単にファイルを分けたのではなく、責務ごとに分離したと考えてください。
動作確認

Swagger UIから、GET /products?limit=1&offset=1を実行して、「マウス」が返ればOKです。
実行結果↓


確認できました。GET /products?limit=1&offset=1が200で成功し、id: 2の「マウス」が返っています。
つまり、get_pagination()をdependencies.pyへ移した後も、
リクエスト
↓
Depends(get_pagination)
↓
dependencies.py の get_pagination()
↓
pagination
↓
routers/products.py の get_products()
という依存関係が正常に動いています。
自力課題

今回はSTEP 4の自力課題で作成したBook CRUD APIを題材にしましょう。
課題内容

現在main.pyにあるBook関連の処理を、商品APIと同じ考え方で整理してください。
完成時の構成は、次のイメージです。
app/
├── main.py
├── dependencies.py
├── routers/
│ ├── products.py
│ └── books.py
└── schemas/
├── products.py
└── books.py
今回、自力で対応してほしい内容は次の4点です。
- Book APIを
routers/books.pyへ移す - Book用のPydanticモデルを
schemas/books.pyへ移す main.pyからBook Routerを登録する- Swagger UIからBook APIがこれまでどおり動作することを確認する
さらに、Book一覧APIがある場合は、get_paginationをDependsで利用して、商品一覧と同じようにlimitとoffsetを利用できるようにしてみてください。
完了条件

Swagger UI上で商品APIとBook APIの両方が表示され、BookのCRUDが正常に動作すること。
また、main.pyにはBookのCRUD処理やPydanticモデルを直接書かず、FastAPIアプリの作成とRouter登録が中心になっている状態を目指してください。
実装結果
課題対応後のmain.py↓
from fastapi import FastAPI
from pydantic import BaseModel, Field
from app.routers.books import router as books_router
from app.routers.products import router as products_router
class UserCreate(BaseModel):
name: str
age: int = Field(gt=0)
email: str | None = None
# FastAPIアプリケーションを作成する
app = FastAPI()
# 商品APIのRouterをFastAPIアプリへ登録する
app.include_router(products_router)
# 書籍APIのRouterをFastAPIアプリへ登録する
app.include_router(books_router)
# GET / にアクセスされたときの処理を定義する
@app.get("/")
def read_root():
return {"message": "Hello, FastAPI!"}
@app.get("/hello")
def read_hello():
return {"message": "Hello from my first API!"}
@app.get("/users/{user_id}")
def get_user(user_id: int):
return {
"user_id": user_id,
}
@app.get("/users")
def get_users(limit: int | None = None):
return {
"limit": limit,
}
@app.get("/users/{user_id}/items")
def get_user_items(
user_id: int,
limit: int = 10,
):
return {
"user_id": user_id,
"limit": limit,
}
@app.post("/users")
def create_user(user: UserCreate):
# リクエストボディから受け取ったユーザーデータをそのまま返す
return user
routers/books.py↓
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, status
from app.dependencies import get_pagination
from app.schemas.books import BookCreate, BookUpdate
router = APIRouter()
books = {}
@router.get("/books")
def get_books(
pagination: Annotated[dict, Depends(get_pagination)],
):
# ページング条件を取得する
limit = pagination["limit"]
offset = pagination["offset"]
# 書籍IDを含めた一覧データを作成する
book_list = [
{
"id": book_id,
**book,
}
for book_id, book in books.items()
]
# offsetの位置からlimit件の書籍を返す
return book_list[offset : offset + limit]
@router.post(
"/books",
status_code=status.HTTP_201_CREATED,
)
def create_book(book: BookCreate):
# 現在の書籍IDの最大値から、新しい書籍IDを作成する
new_book_id = max(books.keys(), default=0) + 1
# Pydanticモデルを辞書に変換する
book_data = book.model_dump()
# 新しい書籍を保存する
books[new_book_id] = book_data
# 作成された書籍IDと書籍データを返す
return {
"id": new_book_id,
**book_data,
}
@router.get("/books/{book_id}")
def get_book(book_id: int):
book = books.get(book_id)
# 書籍が存在しない場合は404エラーを返す
if book is None:
raise HTTPException(
status_code=404,
detail="Book not found",
)
return book
@router.put("/books/{book_id}")
def update_book(book_id: int, book: BookUpdate):
# 書籍が存在しない場合は404エラーを返す
if book_id not in books:
raise HTTPException(
status_code=404,
detail="Book not found",
)
# 指定された書籍IDのデータを更新する
books[book_id] = book.model_dump()
return books[book_id]
@router.delete("/books/{book_id}")
def delete_book(book_id: int):
# 書籍が存在しない場合は404エラーを返す
if book_id not in books:
raise HTTPException(
status_code=404,
detail="Book not found",
)
# 指定された書籍IDのデータを削除する
deleted_book = books.pop(book_id)
return deleted_book
@router.get("/books/{book_id}/reviews")
def get_book_reviews(
book_id: int,
limit: int = 10,
offset: int | None = None
):
return {
"book_id": book_id,
"limit": limit,
"offset": offset,
}
schemas/books.py↓
from pydantic import BaseModel, Field
class BookCreate(BaseModel):
title: str
price: int = Field(gt=0)
description: str | None = None
# STEP4 自力課題 書籍更新用
class BookUpdate(BaseModel):
title: str
price: int = Field(gt=0)
description: str | None = None
Swagger UI上で商品APIとBook APIの両方が表示されていることを確認↓

以下の順にAPIを実行して動作確認していきます。
POST /booksで書籍を2件登録GET /books?limit=1&offset=1で2件目だけ取得できることを確認GET /books/{book_id}で登録した書籍を取得PUT /books/{book_id}で更新DELETE /books/{book_id}で削除- 削除したIDをGETして
404になることを確認
POSTリクエストの結果(2件登録)↓

GET /books?limit=1&offset=1で2件目だけ取得した結果↓

id:1の書籍を取得した結果↓

id:1の書籍を更新した結果↓

id:1の書籍を削除した結果↓

削除したIDを取得した結果↓

ChatGPT先生のレビュー結果

確認できました。自力課題は問題なく完了です。
今回の自力課題では、APIRouterによる分割、schemasへのPydanticモデル分離、Dependsの再利用、main.pyからのRouter登録まで自力で組み合わせて実装できています。
まとめ
今回はschemas・routers・dependenciesに分けて構成を整理しました。
schemas:APIで扱うデータ構造をPydanticモデルとして定義
routers:APIのエンドポイントと、その入口となる処理を定義
dependencies:Dependsで利用する依存処理・依存値を定義
ただ単にファイルを分割するのではなく、それぞれの責務を考えて分割することが重要であると学びました。
実務で扱う上で、可読性や保守性を高めるために、きちんと責務で分離してきれいな構成を作ることはかなり大事になりそうだと感じました。しっかり理解して使いこなせるようにしたいです。
APIRouterについて学んだことの整理APIRouterに機能ごと(書籍、商品、ユーザーなど)にAPIを登録するAPIRouterは各機能のAPIをグループ化したようなイメージ
(書籍用のAPIRouter、商品用のAPIRouter、ユーザー用のAPIRouter)APIRouterにAPIを登録しただけでは、FastAPIアプリ本体にはまだそのAPIが登録されていないinclude_router()でAPIRouterをFastAPI本体のルーティングへ組み込む。- これにより、
APIRouterに登録したAPIがFastAPIアプリから利用できるようになる。
Dependsについて学んだことの整理- APIの処理を実行するために必要な処理や値を、
Dependsを使って依存関係として宣言できる - 今回の学習の内容の場合、リクエストが来たら以下のような順序で処理が実施される。
リクエスト
↓
FastAPIが Depends(get_pagination) を確認
↓
get_pagination(limit, offset) の引数をリクエストから解決
↓
get_pagination() を実行
↓
戻り値を pagination に渡す
↓
get_books() / get_products() を実行
- 単に共通処理だから分離するというだけでなく、ページングの処理をAPIから切り離すことで、本来のAPIの機能に集中でき、責務を分離することができる
正直、現時点ではまだ理解しきれていないため、しっかりと理解して使いこなせるように復習したいと思います。
コメント