当サイトは、アフィリエイト広告を利用しています
FastApiでMongoDBを使ったREST APIをつくる際に
Beanieを使うことで簡単に実装、操作できたのでその方法を忘備録として残す。
当記事では この記事では
についてをまとめる。
Beanieは、PythonでのMongoDB用の非同期ODM (Object Document Mapper)アクセスライブラリ。
Pythonの非同期フレームワークであるFastApiでMongoDBとの通信を実装する場合は
効率的に実装することができる。
Object Document Mapper(ODM)とは、NoSQLデータベース(特にMongoDBなど)において、
データベースのドキュメントとプログラム上のオブジェクトを対応させて操作する仕組みのこと。
具体的には下記ができる
ODMを使うことで、MongoDBのドキュメントをPythonのクラスのオブジェクトとして扱うことができる。
たとえばUserクラスのインスタンスをそのままドキュメントとしてDBに登録することができる。
from beanie import Documentfrom pydantic import BaseModel, Field# eanieドキュメントモデルクラスの作成class User(Document):name: stremail: str = Field(..., regex=r'^\S+@\S+\.\S+$') # メールアドレスのバリデーション付き# データベースに保存async def create_user():# インスタンス作成user = User(name="Alice", email="alice@example.com")# 登録実行await user.insert()
Documentを継承したクラスを作成し、
そのインスタンスをそのままドキュメントとしてDBに登録できるため、
データとその操作方法を一元管理できる。
ODMは、データモデルをプログラム上のデータモデルクラスとして定義するので
データの型チェックや制約をクラスのインスタンス生成時に行うことができる
たとえば、ユーザーのメールアドレスの形式や名前の最大長などのバリデーションが、
データモデル内で自動的に行わる
from beanie import Documentfrom pydantic import BaseModel, Field# eanieドキュメントモデルクラスの作成class User(Document):name: stremail: str = Field(..., regex=r'^\S+@\S+\.\S+$') # メールアドレスのバリデーション付き# データベースに保存async def create_user():# インスタンス作成user = User(name="Alice")# 登録実行await user.insert()
Userの必須属性であるemailがないためエラーが発生する
このようにデータモデルでバリデーションを実施できるため、
データの整合性を保ちながら操作が可能になる。
ODMは、MongoDBのようなNoSQLデータベースのクエリ構文を抽象化し、Pythonのメソッドを使って
直感的にクエリ操作ができるようする。そのため
MongoDBの特定の構文や操作を知らなくても、オブジェクト指向の操作でデータを操作できる
from beanie import Documentfrom pydantic import BaseModel, Field# eanieドキュメントモデルクラスの作成class User(Document):name: stremail: str = Field(..., regex=r'^\S+@\S+\.\S+$') # メールアドレスのバリデーション付き# データベースに保存async def create_user():# インスタンス作成user = User(name="Alice", email="alice@example.com")# 登録実行await user.insert()
ドキュメントクラスのインスタンスのメソッドでクエリを発行できるので
MongoDBの特定の構文や操作がわからなくでも操作可能になる
Beanieを使うことで下記のようなメリットがある
Beanieは、Pythonのasync
/await
構文を使用して、MongoDBとの非同期通信を行うので
複数のデータベース操作を並行して処理でき、待機時間が短縮され、アプリケーションのパフォーマンスが向上する
特に高負荷なアプリケーションや、複数のリクエストが並行して処理されるWebアプリケーション(FastAPIなど)に適している。
from beanie import Document, init_beaniefrom motor.motor_asyncio import AsyncIOMotorClientimport asyncioclass User(Document):name: stremail: strasync def main():# MongoDBクライアントを設定client = AsyncIOMotorClient("mongodb://admin:admin123@172.25.2.3:27017/")# Beanieでデータベースを初期化database = client["mongodb"]await init_beanie(database=database, document_models=[User])# 非同期にユーザーを挿入await User(name="Alice", email="alice@example.com").insert()# 非同期にユーザーを検索user = await User.find_one(User.name == "Alice")print(user.email)# 非同期で実行if __name__ == "__main__":asyncio.run(main())
insert()やfind_one()といった操作を非同期で行うことで、効率的なデータ処理ができる。
Beanieは、Pydanticモデルと連携しているため、ドキュメントをPydanticモデルとして定義できる
FastAPIなどPydanticをネイティブにサポートしているフレームワークとの相性がいい。
from beanie import Documentfrom pydantic import Fieldclass User(Document):name: str = Field(..., min_length=2, max_length=50)email: str = Field(..., regex=r'^\S+@\S+\.\S+$')# `name`が1文字未満または51文字以上、`email`が無効な形式の場合、バリデーションエラーを発生user = User(name="J", email="invalid_email")
名前が2文字以上50文字以下、メールアドレスが有効な形式であることをPydanticが自動的に検証してくれる
※データのバリデーションとスキーマ管理と同じ内容
MongoDBはドキュメントベースのデータベースであり、SQLデータベースのように外部キーによるリレーションは
一般的ではないが、Beanieはリレーションをサポートしている。
Beanieでは、別のドキュメントをフィールドとして持ち、リレーションのように利用できる。
from typing import Listfrom beanie import Document, Link, init_beaniefrom motor.motor_asyncio import AsyncIOMotorClientimport asyncioclass Post(Document):title: strcontent: strclass User(Document):name: stremail: strposts: List[Link[Post]] # リレーションとしてPostのリストを定義async def main():# MongoDBクライアントを設定client = AsyncIOMotorClient("mongodb://admin:admin123@172.25.2.3:27017/")# Beanieでデータベースを初期化database = client["mongodb"]await init_beanie(database=database, document_models=[User, Post])# postに登録post1 = await Post(title="First Post", content="Content of first post").insert()post2 = await Post(title="Second Post", content="Content of second post").insert()# UserドキュメントをPostドキュメントにリンクさせるuser = await User(name="Bob", email="bob@example.com", posts=[post1, post2]).insert()# リレーションをたどってPostデータにアクセスuser_with_posts = await User.get(user.id, fetch_links=True)for post in user_with_posts.posts:print(post.title) # "First Post" と "Second Post" が出力される# 非同期で実行if __name__ == "__main__":asyncio.run(main())
Link型を利用して他のドキュメントとのリレーションを定義できる
fetch_links=True`を指定すると、リレーション先のドキュメントを自動的に取得する
beanieの使い方を解説する。
pipまたはpoetryを使ってbeanieをインストールしておく
beanieを使う流れは
のようになる。
from beanie import Document, init_beaniefrom motor.motor_asyncio import AsyncIOMotorClientimport asyncio# Beanieドキュメントモデルクラスの作成class User(Document):name: stremail: strasync def main():# MongoDBクライアントを設定client = AsyncIOMotorClient("mongodb://admin:admin123@172.25.2.3:27017/")# Beanieでデータベースを初期化database = client["mongodb"]await init_beanie(database=database, document_models=[User])# Documentクラスの操作_ユーザーを挿入await User(name="Alice", email="alice@example.com").insert()# Documentクラスの操作_ユーザーを検索user = await User.find_one(User.name == "Alice")print(user.email)# 非同期で実行if __name__ == "__main__":asyncio.run(main())
詳しく解説する
# MongoDBクライアントを設定client = AsyncIOMotorClient("mongodb://admin:admin123@172.25.2.3:27017/")
MongoDBへの非同期接続を確立する。
# Beanieでデータベースを初期化database = client["mongodb"]await init_beanie(database=database, document_models=[User])
init_beanieを使って
指定することでBeanieが内部的にMongoDBとの関連付けを行う。
これにより、Beanieドキュメントモデルクラスを使ってMongoDB上の指定したデータベース内で
コレクションを操作できるようになる。
Documentクラスのモデルは複数設定することができる
# Documentクラスの操作_ユーザーを挿入await User(name="Alice", email="alice@example.com").insert()# Documentクラスの操作_ユーザーを検索user = await User.find_one(User.name == "Alice")
BeanieドキュメントモデルクラスはDocumentを継承しているため、
MongoDBに対する基本的な操作(insert、find_oneなど)が可能。
Beanieでは、Documentを継承したクラスの名前が自動的にコレクション名になる。
Userというクラスを定義すると、MongoDB上でuserというコレクションが自動的に作成され、
そのコレクションがUserクラスのインスタンス操作とリンクされる。
from beanie import Document, init_beaniefrom motor.motor_asyncio import AsyncIOMotorClientimport asyncio# Beanieドキュメントモデルクラスの作成class User(Document):name: stremail: strasync def main():# MongoDBクライアントを設定client = AsyncIOMotorClient("mongodb://admin:admin123@172.25.2.3:27017/")# Beanieでデータベースを初期化database = client["mongodb"]await init_beanie(database=database, document_models=[User])# Documentクラスの操作_ユーザーを挿入await User(name="Alice", email="alice@example.com").insert()# Documentクラスの操作_ユーザーを検索user = await User.find_one(User.name == "Alice")print(user.email)# 非同期で実行if __name__ == "__main__":asyncio.run(main())
を実行した場合はUserコレクションが作成される。
mongo_expressで確認すると
となっている。
mongo_expressについては下記記事参照
class User(Document):name: stremail: strclass Settings:name = "user_collection" # MongoDBで使用するコレクション名
のようにすれば任意のコレクションに任意の名前をつけることができる。
beanieを使ってMongoDBを操作する方法をまとめた。
MongoDBを使う方法は他にpymongo等を使う方法もあるが
両方使った上での感想としてはbeanieの方が使いやすく、実装も楽になる。
FastApiでMongoDBを使う場合はbeanieを使うのが良さそう。