FastAPIでCRUD APIエンドポイントを作成する
はじめに
FastAPIの学習でCRUD APIを作成してみました。
FastAPI は、Python 3.8+ で API を構築するためのWeb フレームワークです。
環境
Docker
Python 3.8+
MongoDB
tl;dr
- 仮想環境を作成する
- Dockerで環境構築する
- DBの設定を行う
- モデルを作成する
- エンドポイントを作成する
- CRUD操作を行う
- SwaggerUIを使って検証する
fastapiのディレクトリ構造
最低限の構造になりますが。
. # ルートディレクトリ
├── README.md
├── app
│ ├── __init__.py # 空で大丈夫。Pythonがファイルを含むディレクトリをパッケージとして扱うために必要
│ ├── main.py # スターターファイル
│ ├── __pycache__
│ ├── config
│ ├── models
│ ├── routes
│ └── schemas
├── docker-compose.yaml
├── Dockerfile
├── requirements.txt # 必要なパッケージを記述する場所
├── .env
└── venv # 仮想環境
仮想環境を作成する
ルートディレクトリから作成します。
# pythonのバージョンを確認する
➜ python3 -V
Python 3.10.11
# venvを使って仮想環境を作成する
➜ python3 -m venv venv
# 作成されたことを確認する
➜ ls
venv
仮想環境を起動します。
# mac/linuxの場合
source venv/bin/activate
(venv) ➜
仮想環境を中止する場合deactivate
を打ちます。
必要なパッケージを追加する
requirements.txt
を作成し、こちらのパッケージを追加します。
asgiref==3.4.1
click==8.0.1
dnspython==1.16.0
fastapi==0.68.1
h11==0.12.0
pydantic==1.8.2
pymongo==3.12.0
six==1.16.0
starlette==0.14.2
typing-extensions==3.10.0.0
uvicorn==0.15.0
パッケージ | 用途 |
---|---|
asgiref | ASGI(Asynchronous Server Gateway Interface)のリファレンス実装 |
click | コマンドラインツールの作成を支援するライブラリ |
dnspython | DNSプロトコルの実装を提供するライブラリ |
fastapi | 高性能なAPIを作成するためのWebフレームワーク |
h11 | HTTP/1.1のためのPythonライブラリ |
pydantic | データバリデーションのためのライブラリ |
pymongo | MongoDBデータベースとのやり取りのためのライブラリ |
six | Python 2と3の互換性を提供するためのライブラリ |
starlette | ASGIアプリケーションの構築を支援するライブラリ |
typing-extensions | 型ヒントのためのPython拡張ライブラリ |
uvicorn | ASGIアプリケーションを実行するためのサーバー |
Dockerで環境構築する
# ベースイメージ
FROM python:3.9.7
# カレントディレクトリを/appに設定する
# ここにrequirements.txtファイルとappディレクトリを置く
WORKDIR /app
# このファイルは頻繁に変更されるものではないので、Dockerはそれを検知してこのステップでキャッシュを使用し、次のステップでもキャッシュを有効にする
COPY requirements.txt requirements.txt
# パッケージをインストールする
RUN pip install --no-cache-dir --upgrade -r requirements.txt
# カレントディレクトリを/appディレクトリの中にコピーする。
# このディレクトリにはすべてのコードがあり、最も頻繁に変更されるものなので、コンテナ・イメージのビルド時間を最適化するために、これをDockerfileの最後の方に置く
COPY . .
# uvicornサーバーを起動する
# app.mainからappをインポートする
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
no-cache-dir
オプションは、ダウンロードしたパッケージをローカルに保存しないようにpipに指示します。これは、同じパッケージをインストールするためにpipを再度実行する場合のみですが、コンテナで作業する場合はそうではありません。
no-cache-dir
はpipに関連するオプションで、Dockerやコンテナとは関係ないです。
upgrade
オプションは、pipにパッケージをアップグレードするように指示します。
このステップでも、利用可能な場合はDockerキャッシュを使用します。
キャッシュを使用すると、開発中にイメージを何度もビルドする際に、毎回すべての依存関係をインストールすることなく、時間を節約できます。
version: '3'
services:
fastapi:
build: .
volumes:
- ./:/app
ports:
- 8000:8000 # ホストマシンのポート8000を、docker内のポート8000に接続する
コンテナのビルドと起動を行います。
(venv) ➜ docker-compose up --build -d
[+] Building 2.0s (10/10) FINISHED
=> [fastapi internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [fastapi internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 266B 0.0s
=> [fastapi internal] load metadata for docker.io/library/python:3.9.7 1.3s
=> [fastapi 1/5] FROM docker.io/library/python:3.9.7@sha256:8771691756bbf5beff80d64fca8 0.0s
=> [fastapi internal] load build context 0.1s
=> => transferring context: 167.89kB 0.1s
=> CACHED [fastapi 2/5] WORKDIR /app 0.0s
=> CACHED [fastapi 3/5] COPY requirements.txt requirements.txt 0.0s
=> CACHED [fastapi 4/5] RUN pip install --no-cache-dir --upgrade -r requirements.txt 0.0s
=> [fastapi 5/5] COPY . . 0.3s
=> [fastapi] exporting to image 0.2s
=> => exporting layers 0.2s
=> => writing image sha256:28782418da17882527ead598b19afdaa041fb8a271522d0f9e454abb087d 0.0s
=> => naming to docker.io/library/fastapi-tut-fastapi 0.0s
[+] Running 2/2
✔ Network fastapi-tut_default Created 0.0s
✔ Container fastapi-tut-fastapi-1 Started 0.5s
DBの設定を行う
DBを作成する
cloud.mongodb.comへアクセスし、アカウントを作成します。
Build a DatabaseをクリックしDBを作成します。
Freeプランを選びます。
名前を入れてcreateをクリックします。
DBのユーザー名とパスワードを入れてCreate Userをクリックします。
Add my current IPAdressをクリックしてIPアドレスを取得します。
DBクラスタを作成されました。
ConnectをクリックしてDBに接続します。
DBを追加する
Access your data through toolsの下にいくつの接続手段がありますが自分の開発環境に合う手段で大丈夫です。
自分はCLIから接続しました。
接続しないでウェブページ上から確認することもできます。
Connect to your ApplicationのメニューをクリックしたらDBのURLが表示されます。
コピーします。
アプリ内に.env
を作成し、DBのURLを追加します。
DBのパスワードを書き換える必要があるので忘れないでくださいー
DB_URl=*************
環境変数を使うにはpython-dotenv
というパッケージが必要なのでインストールします。
pip3 install python-dotenv
pip freeze > requirements.txt
config/database.py
内にURLを追加する
# MongoDBクライエントをインポートする
from pymongo import MongoClient
# dotenvをインポートする
from dotenv import dotenv_values
config = dotenv_values(".env")
# DB URLを追加する
client = MongoClient(config.get("DB_URL"))
# DB名を追加する
db = client.get_database("fastapi")
# コレクションを作成する
collection_name = db.get_collection("todos")
コレクションは MongoDB ドキュメントのグループです。リレーショナルデータベースのテーブルに似てます。
モデルを作成する
名前、説明、完了かどうか、日付のフィールドを持つ Todo という Pydantic モデルを定義します。
PydanticモデルはFastAPIで一般的に使用され、入力されたリクエストデータを検証し、モデルの構造に基づいてAPIドキュメントを自動生成します。
from datetime import datetime
from pydantic import BaseModel
class Todo(BaseModel):
name: str
description: str
completed: bool
date: datetime
スキーマファイルを作成する
def todo_serializer(todo) -> dict:
return {
"id": str(todo["_id"]),
"name": todo["name"],
"description": todo["description"],
"completed": todo["completed"],
"date": todo["date"],
}
def todos_serializer(todos) -> list:
return [todo_serializer(todo) for todo in todos]
todo_serializer
と todos_serializer
2つの関数を定義しました。
これらの関数は MongoDB ドキュメント (todos) を簡単に JSON に変換できる形式にシリアライズする役割を担っています。
ObjectIdは、各ドキュメントの_id
フィールドのデフォルト値で、ドキュメントの作成時に生成されます。
MongoDB の ObjectId
は直接 JSON にシリアライズできないので、todo_serializer
関数の中で str(todo["_id"])
を使って "_id"
フィールドを文字列に変換しています。
エンドポイントを作成する
TODOのCRUDオペレーションを行うAPIエンドポイントを追加します。
from fastapi import APIRouter
# モデル、DB、スキーマをインポートする
from app.models.todos_model import Todo
from app.config.database import collection_name
from app.schemas.todos_schema import todos_serializer, todo_serializer
from bson import ObjectId
# ルートを定義する
todo_api_router = APIRouter()
# GET
@todo_api_router.get("/")
async def get_todos():
todos = todos_serializer(collection_name.find())
return todos
@todo_api_router.get("/{id}")
async def get_todo(id: str):
return todos_serializer(collection_name.find_one({"_id": ObjectId(id)}))
# POST
@todo_api_router.post("/")
async def create_todo(todo: Todo):
_id = collection_name.insert_one(dict(todo))
return todos_serializer(collection_name.find({"_id": _id.inserted_id}))
# UPDATE
@todo_api_router.put("/{id}")
async def update_todo(id: str, todo: Todo):
collection_name.find_one_and_update({"_id": ObjectId(id)}, {
"$set": dict(todo)
})
return todos_serializer(collection_name.find({"_id": ObjectId(id)}))
# DELETE
@todo_api_router.delete("/{id}")
async def delete_todo(id: str):
collection_name.find_one_and_delete({"_id": ObjectId(id)})
return {"status": "ok"}
-
GET /:
-
@todo_api_router.get("/")
は、TODOリストの全体を取得するためのエンドポイントです。 -
get_todos
関数はcollection_name.find()
を呼び出して、データベース内のすべてのTODOを取得します。 - 取得したTODOは
todos_serializer
を使用してシリアライズされ、JSON形式で返されます。
-
-
GET /{id}:
-
@todo_api_router.get("/{id}")
は、特定のIDに基づいてTODOを取得するためのエンドポイントです。 -
get_todo
関数はcollection_name.find_one({"_id": ObjectId(id)})
を呼び出して、指定されたIDのTODOを取得します。 - 取得したTODOも
todos_serializer
を使用してシリアライズされ、JSON形式で返されます。
-
-
POST /:
-
@todo_api_router.post("/")
は、新しいTODOを作成するためのエンドポイントです。 -
create_todo
関数はcollection_name.insert_one(dict(todo))
を呼び出して、新しいTODOをデータベースに挿入します。 - 挿入されたTODOは
_id
を使用して再びデータベースから取得され、todos_serializer
を使用してシリアライズされ、JSON形式で返されます。
-
-
PUT /{id}:
-
@todo_api_router.put("/{id}")
は、指定されたIDのTODOを更新するためのエンドポイントです。 -
update_todo
関数はcollection_name.find_one_and_update({"_id": ObjectId(id)}, {"$set": dict(todo)})
を呼び出して、指定されたIDのTODOを更新します。 - 更新されたTODOは再びデータベースから取得され、
todos_serializer
を使用してシリアライズされ、JSON形式で返されます。
-
-
DELETE /{id}:
-
@todo_api_router.delete("/{id}")
は、指定されたIDのTODOを削除するためのエンドポイントです。 -
delete_todo
関数はcollection_name.find_one_and_delete({"_id": ObjectId(id)})
を呼び出して、指定されたIDのTODOを削除します。 - 削除が成功した場合、
{"status": "ok"}
がJSON形式で返されます。
-
これらのエンドポイントを使用することで、TODOリストの作成、取得、更新、削除などの操作が可能になります。
MongoDBのCRUDメソッドについて公式ドキュメントをご参考ください。
エンドポイントをインポートする
エンドポイントを作成したがmain.py
からアクセスできるようにインポートします。
from fastapi import FastAPI
from app.routes.todos_route import todo_api_router
app = FastAPI()
app.include_router(todo_api_router)
localhost:8000/docs
へアクセスし、こちらの画面を表示されたらOKです。
SwaggerUIを使って検証する
Swagger UIは、OpenAPI Specification(以前はSwagger Specificationとしても知られていました)に基づいてAPIドキュメンテーションを提供するオープンソースのユーザーインターフェースです。
Swagger(またはOpenAPI)は、APIの記述、ドキュメンテーション、とクライアントとの相互作用を可能にするための標準の仕様です。
Swagger UIは、APIエンドポイント、操作、パラメータ、とレスポンスの情報を視覚的かつ対話的に表示できます。主に以下のような機能があります:
-
APIの可視化: APIエンドポイントや操作の一覧を表示し、各エンドポイントの詳細情報に簡単にアクセスできます。
-
リクエストのテスト: Swagger UIを使用してAPIエンドポイントにリクエストを送信し、レスポンスを確認できます。これにより、APIが期待どおりに動作するかを簡単にテストできます。
-
自動生成されたAPIドキュメンテーション: Swagger(またはOpenAPI)で提供されるAPI仕様を元に、自動的に生成されたAPIドキュメンテーションが表示されます。
-
レスポンスの確認: 各APIエンドポイントがどのような種類のレスポンスを返すかを確認できます。これにはステータスコードやJSONスキーマなどが含まれます。
TODO一覧を取得する
MongoDB側でCollectionsタブをクリックし、TODOを表示されていることも確認します。
TODOを作成する
DB上に作成されたことも確認します。
nameを空にして作成するとエラーが出ることも確認します。
{
"detail": [
{
"loc": [
"body",
12
],
"msg": "Expecting value: line 2 column 11 (char 12)",
"type": "value_error.jsondecode",
"ctx": {
"msg": "Expecting value",
"doc": "{\n \"name\": ,\n \"description\": \"string\",\n \"completed\": true,\n \"date\": \"2023-12-09T16:01:21.701Z\"\n}",
"pos": 12,
"lineno": 2,
"colno": 11
}
}
]
}
TODOを更新する
DB上に更新されたことも確認します。
TODOを削除する
DB上に削除されたことも確認します。
終わり
FastAPIでCRUD APIのエンドポイントの実装とDBとの連携を試してみました。
SwaggerUIのお陰で素早くエンドポイントを検証できて便利でしたー
Discussion