当サイトは、アフィリエイト広告を利用しています

【Python × MongoDB】Beanieで簡単にMongoDBを操作する方法

作成日:2024月11月01日
更新日:2024年11月01日

FastApiでMongoDBを使ったREST APIをつくる際に
Beanieを使うことで簡単に実装、操作できたのでその方法を忘備録として残す。

当記事では この記事では

  • beanieとは何なのか?
  • beanieの利点
  • beanieの使い方

についてをまとめる。

beanieとは?

Beanieは、PythonでのMongoDB用の非同期ODM (Object Document Mapper)アクセスライブラリ。
Pythonの非同期フレームワークであるFastApiでMongoDBとの通信を実装する場合は
効率的に実装することができる。

ODM (Object Document Mapper)とは?

Object Document Mapper(ODM)とは、NoSQLデータベース(特にMongoDBなど)において、
データベースのドキュメントとプログラム上のオブジェクトを対応させて操作する仕組みのこと。

具体的には下記ができる

オブジェクト指向のデータ操作

ODMを使うことで、MongoDBのドキュメントをPythonのクラスのオブジェクトとして扱うことができる。
たとえばUserクラスのインスタンスをそのままドキュメントとしてDBに登録することができる。

オブジェクト指向のデータ操作
from beanie import Document
from pydantic import BaseModel, Field
# eanieドキュメントモデルクラスの作成
class User(Document):
name: str
email: 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 Document
from pydantic import BaseModel, Field
# eanieドキュメントモデルクラスの作成
class User(Document):
name: str
email: 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 Document
from pydantic import BaseModel, Field
# eanieドキュメントモデルクラスの作成
class User(Document):
name: str
email: 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を使うことで下記のようなメリットがある

  • 非同期サポート
  • Pydanticモデルのサポート
  • リレーションのサポート

非同期サポート

Beanieは、Pythonのasync/await構文を使用して、MongoDBとの非同期通信を行うので
複数のデータベース操作を並行して処理でき、待機時間が短縮され、アプリケーションのパフォーマンスが向上する
特に高負荷なアプリケーションや、複数のリクエストが並行して処理されるWebアプリケーション(FastAPIなど)に適している。

非同期サポート
from beanie import Document, init_beanie
from motor.motor_asyncio import AsyncIOMotorClient
import asyncio
class User(Document):
name: str
email: str
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])
# 非同期にユーザーを挿入
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()といった操作を非同期で行うことで、効率的なデータ処理ができる。

Pydanticモデルのサポート

Beanieは、Pydanticモデルと連携しているため、ドキュメントをPydanticモデルとして定義できる
FastAPIなどPydanticをネイティブにサポートしているフレームワークとの相性がいい。

python
from beanie import Document
from pydantic import Field
class 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 List
from beanie import Document, Link, init_beanie
from motor.motor_asyncio import AsyncIOMotorClient
import asyncio
class Post(Document):
title: str
content: str
class User(Document):
name: str
email: str
posts: 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の使い方

beanieの使い方を解説する。

インストール

pipまたはpoetryを使ってbeanieをインストールしておく

実装の流れ

beanieを使う流れは

  • MongoDBクライアントの作成
  • init_beanieでの初期化
  • Documentクラスの操作

のようになる。

beanieを使った実装

beanie
from beanie import Document, init_beanie
from motor.motor_asyncio import AsyncIOMotorClient
import asyncio
# Beanieドキュメントモデルクラスの作成
class User(Document):
name: str
email: str
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])
# 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クライアントの作成

MongoDBクライアントの作成
# MongoDBクライアントを設定
client = AsyncIOMotorClient("mongodb://admin:admin123@172.25.2.3:27017/")

MongoDBへの非同期接続を確立する。

init_beanieでの初期化

init_beanieでの初期化
# Beanieでデータベースを初期化
database = client["mongodb"]
await init_beanie(database=database, document_models=[User])

init_beanieを使って

  • MongoDBのデータベース
  • Documentクラスのモデル

指定することでBeanieが内部的にMongoDBとの関連付けを行う。
これにより、Beanieドキュメントモデルクラスを使ってMongoDB上の指定したデータベース内で
コレクションを操作できるようになる。

Documentクラスのモデルは複数設定することができる

Documentクラスの操作

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_beanie
from motor.motor_asyncio import AsyncIOMotorClient
import asyncio
# Beanieドキュメントモデルクラスの作成
class User(Document):
name: str
email: str
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])
# 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で確認すると
2024-10-31-23-06-02

となっている。

mongo_expressについては下記記事参照

コレクションに別名をつける

コレクション別名
class User(Document):
name: str
email: str
class Settings:
name = "user_collection" # MongoDBで使用するコレクション名

のようにすれば任意のコレクションに任意の名前をつけることができる。

まとめ

beanieを使ってMongoDBを操作する方法をまとめた。
MongoDBを使う方法は他にpymongo等を使う方法もあるが
両方使った上での感想としてはbeanieの方が使いやすく、実装も楽になる。

FastApiでMongoDBを使う場合はbeanieを使うのが良さそう。

新着記事

タグ別一覧
top