3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

FastAPI + Mongo + ODMでウェブAPIを簡単に作る

Posted at

はじめに

FastAPIは、Pythonの型ヒントに基づいてAPIを構築するためのWebフレームワークです。DBにRDSを用いる場合の情報はたくさんあるのですが、DBに MongoDB を用いた場合の情報が少なかったので、記述します。

MongoDB へのアクセスライブラリとして beanie を用いました。
Beanie は、MotorPydantic に基づく、MongoDB用の非同期Pythonオブジェクトドキュメントマッパー(ODM)です。
ODMですので、FastAPIのバリデーションや型ヒントとの相性がいいです。

前提条件

  • Python 3.9以上
  • MongoDB 4.x
    • この記事では DB 名を database_name としています

セットアップ

私は、pyenv と pipenv を利用してPythonの仮想環境を構築しています。使い慣れている仮想環境をお持ちの場合、適宜読み替えてください。

$ pyenv local 3.9.10
$ python --version
Python 3.9.10
    
$ pipenv install fastapi                                           
$ pipenv install uvicorn
$ pipenv install python-dotenv

// beanieは、mongoのODMライブラリ
$ pipenv install beanie
$ pipenv install 'pydantic[email]'

ファイルの構成

.
├── .env
├── database.py
├── main.py
└── schemas.py

.env ファイル

mongo サーバにアクセスするURLは、.env ファイルに格納しています。値の取得は python-dotenv ライブラリを利用しています。

MONGO_URL=mongodb://foo:foo_password@localhost:27001/

main.py

main.py は、unicorn から呼び出す FastAPI のメインです。

from datetime import datetime

from fastapi import Depends, FastAPI, HTTPException
from beanie import PydanticObjectId, WriteRules
    
import schemas
from database import init_db
    
app = FastAPI()

# FastAPI起動時にDBにアクセスする環境を初期化しています    
@app.on_event("startup")
async def start_db():
    await init_db()

# ユーザを作成します。     
# response_model で WebAPIのレスポンスの型を指定しています。
@app.post("/users", response_model=schemas.User)
async def create_user(user: schemas.UserCreate):
    
    user = await user.save(link_rule=WriteRules.WRITE) # Itemsも書き込む
    
    if len(user.items) > 0:
        for item in user.items:
            item.owner_id = user.id
    resp_user = await user.save(link_rule=WriteRules.WRITE)
    return resp_user

# idを指定してユーザを取得します    
@app.get("/users/{id}", response_model=schemas.User)
async def get_user(id: PydanticObjectId):
    user = await schemas.User.get(id)
    return user

# ユーザを更新します
@app.put("/users", response_model=schemas.User)
# 更新専用の UserUpdate クラスを用いています。
async def update_user(user: schemas.UserUpdate):
    current_user = await schemas.UserUpdate.get(user.id)
    if current_user is None:
        raise HTTPException(status_code=404, detail="User not found")

    # 更新処理では必要な属性だけを置き換えています
    current_user.updated_at = datetime.now()
    if user.password:
        current_user.password = user.password
    if user.email:
        current_user.email = user.email
    ret_user = await current_user.save()
    return ret_user

# ユーザのリストを取得します
@app.get("/users/", response_model=list[schemas.User])
async def read_users(skip: int = 0, limit: int = 100):
    users = await schemas.User.find().skip(skip).limit(limit).to_list()
    return users

# Itemを生成します。
@app.post("/items", response_model=schemas.Item)
async def create_item(item: schemas.ItemCreate):
    user = await schemas.User.get(item.owner_id)
    if user is None:
        raise HTTPException(status_code=404, detail="User not found")

    user.items.append(item)
    await user.save(link_rule=WriteRules.WRITE)
    return item

database.py

database.pyは、beanie や motor に関する初期化をおこなっています。
init_beanieの引数の database で MongoDB の DB 名を指定してください。

import os
    
from beanie import init_beanie
import motor.motor_asyncio
    
import schemas

async def init_db():
    # MONGO_URLは .env ファイルに定義しています 
    client = motor.motor_asyncio.AsyncIOMotorClient(os.getenv('MONGO_URL'))
    # MongoDBのDBを指定してください。
    await init_beanie(database=client.database_name,
       # 利用したい ODM を指定します
       document_models=[ 
           schemas.UserBase,schemas.User,schemas.UserCreate,schemas.UserUpdate,
           schemas.ItemBase,schemas.ItemCreate,schemas.Item,schemas.ItemUpdate
       ])

schema.py

schema.py は、Pydantic形式で記述する beanie の ODMモデルを定義しています。

from datetime import datetime
from typing import Optional

from beanie import Document, Link, PydanticObjectId
from pydantic import EmailStr, Field

# Itemの共通属性
class ItemBase(Document):
    # Pydantic では、':' はタイプを宣言するとき
    title: str
    # Pydantic では、'=' は値を設定するとき
    description: Optional[str] = None
    owner_id: Optional[PydanticObjectId]

    created_at: datetime = Field(default_factory=datetime.now)
    updated_at: datetime = Field(default_factory=datetime.now)

    class Settings:     # Settingsは beanie 用
        name = "items"  # mongoのコレクション名を指定

    class Config:       # Configは Pydantic 用
        # orm_mode=Trueとすることで、Item.id というアクセス可能。
        # ゆえに FastAPIの response_model に指定できます
        orm_mode = True

# 生成時と通常アクセス時とのクラスは分けておくのがベター
class ItemCreate(ItemBase):
    pass
class Item(ItemBase):
    created_at: Optional[datetime]
    updated_at: Optional[datetime]
    class Config:
        schema_extra = {
            "example": {
                "title": "book",
                "description": "ストレングス・ファインダー",
            }
        }

class ItemUpdate(Item):
    id: PydanticObjectId    # 更新は、idが必須


# User の共通属性
class UserBase(Document):
    email: EmailStr = Field(...)
    items: Optional[list[Link[Item]]] = []      # Itemのリレーション
    created_at: datetime = Field(default_factory=datetime.now)
    updated_at: datetime = Field(default_factory=datetime.now)

    class Settings:
        name = "users"

    class Config:       # Config は Pydantic 用
        orm_mode = True

# 生成時にはパスワードが必須。わかりやすい
class UserCreate(UserBase):
    # セキュリティを確保するため、パスワード属性は UserCreate クラスで定義し
    # Userクラスには定義しない。
    password: str = Field(...)


# Userクラスに passwordを含めない。そうすることでデータ読み取り時に
# パスワードが送信されることを防ぐ。
class User(UserBase):
    is_active: bool = False
    created_at: Optional[datetime]
    updated_at: Optional[datetime]

    class Config:       # Config は Pydantic 用
        schema_extra = {
            "example": {
                "email": "taro.example.com",
                "is_active": True,
                "items": [ { "title": "タイトル" } ]
            }
        }

class UserUpdate(User):
    id: PydanticObjectId    # 更新は、idが必須
    password: Optional[str] # パスワードはオプション

起動方法

$ pipenv shell
$ uvicorn main:app --port 3000 --reload
.....

動作の確認

FastAPIはOpenAPIのインタフェースを持っているので、ブラウザを用いて各APIの動作確認を行うことができます。

ブラウザで、http://localhost:3000/docs にアクセスすると、下記の画面が表示されます。

このページから、Userの生成や取得などがおこなえるので、動作の確認を行ってください。

ss 2022-07-10 15.28.54.png

参考資料

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?