mcpField
B
Beanie ODM

Beanie ODM

github.com/thelobbiデータ・データベース210 回インストール1 日前に更新
リポジトリ

Beanie ODM について

Beanie ODM と非同期 Motor ドライバーで MongoDB をアプリケーションに接続するためのパターンを紹介するスキルです。特に FastAPI 向けに、ドキュメント設計、クエリ、集計、インデックス、トランザクション、サービス層を扱います。

主な機能

  1. 01MongoDB 接続の初期化
  2. 02ドキュメントとリレーションの定義
  3. 03CRUD と高度なクエリの実行
  4. 04集計パイプラインの作成
  5. 05インデックスとトランザクションの設定
  6. 06サービス層の構成

活用シーン

  1. 01Beanie でアプリケーションデータをモデル化
  2. 02MongoDB ドキュメントの検索と集計
  3. 03インデックスとトランザクション処理の追加

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/thelobbi/claude --skill beanie-odm

インストール後、Claude Code で/beanie-odmを入力して読み込まれたか確認します。

詳細

MongoDB 用 Beanie ODM

このスキルでは、FastAPI アプリケーション向けに最適化された、非同期 Motor ドライバーと Beanie ODM を使用した MongoDB 統合のパターンを紹介します。

データベースの初期化

接続の設定
from beanie import init_beanie
from motor.motor_asyncio import AsyncIOMotorClient
from app.domains.users.models import User
from app.domains.products.models import Product

async def init_database(settings: Settings):
    client = AsyncIOMotorClient(settings.mongodb_url)

    await init_beanie(
        database=client[settings.database_name],
        document_models=[
            User,
            Product,
            # Add all document models
        ]
    )
設定の構成
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    mongodb_url: str = "mongodb://localhost:27017"
    database_name: str = "app_db"

    class Config:
        env_file = ".env"

ドキュメントモデル

基本的なドキュメント
from beanie import Document, Indexed
from pydantic import Field, EmailStr
from datetime import datetime
from typing import Optional

class User(Document):
    email: Indexed(EmailStr, unique=True)
    name: str
    hashed_password: str
    is_active: bool = True
    created_at: datetime = Field(default_factory=datetime.utcnow)
    updated_at: datetime = Field(default_factory=datetime.utcnow)

    class Settings:
        name = "users"  # Collection name
        use_state_management = True

    class Config:
        json_schema_extra = {
            "example": {
                "email": "[email protected]",
                "name": "John Doe"
            }
        }
リレーションシップを持つドキュメント
from beanie import Document, Link, BackLink
from typing import List, Optional

class Author(Document):
    name: str
    books: List[BackLink["Book"]] = Field(original_field="author")

    class Settings:
        name = "authors"

class Book(Document):
    title: str
    author: Link[Author]
    categories: List[Link["Category"]] = []

    class Settings:
        name = "books"

class Category(Document):
    name: str
    books: List[BackLink[Book]] = Field(original_field="categories")

    class Settings:
        name = "categories"
埋め込みドキュメント
from beanie import Document
from pydantic import BaseModel
from typing import List

class Address(BaseModel):
    street: str
    city: str
    country: str
    postal_code: str

class Contact(BaseModel):
    type: str  # "email", "phone"
    value: str
    is_primary: bool = False

class Customer(Document):
    name: str
    addresses: List[Address] = []
    contacts: List[Contact] = []

    class Settings:
        name = "customers"

クエリパターン

基本的な CRUD 操作
# Create
user = User(email="[email protected]", name="John")
await user.insert()

# Create with validation
user = await User.insert_one(
    User(email="[email protected]", name="John")
)

# Read by ID
user = await User.get(user_id)

# Read with filter
users = await User.find(User.is_active == True).to_list()

# Update
user.name = "Jane"
await user.save()

# Partial update
await user.set({User.name: "Jane", User.updated_at: datetime.utcnow()})

# Delete
await user.delete()
高度なクエリ
from beanie.operators import In, RegEx, And, Or

# Find with operators
active_users = await User.find(
    And(
        User.is_active == True,
        User.created_at >= start_date
    )
).to_list()

# Regex search
users = await User.find(
    RegEx(User.name, "^John", options="i")
).to_list()

# In operator
users = await User.find(
    In(User.email, ["[email protected]", "[email protected]"])
).to_list()

# Pagination
users = await User.find_all().skip(20).limit(10).to_list()

# Sorting
users = await User.find_all().sort(-User.created_at).to_list()

# Projection (select specific fields)
users = await User.find_all().project(UserSummary).to_list()

集約パイプライン

from beanie import PydanticObjectId

class UserStats(BaseModel):
    total_users: int
    active_users: int
    avg_age: float

# Aggregation pipeline
pipeline = [
    {"$match": {"is_active": True}},
    {"$group": {
        "_id": None,
        "total": {"$sum": 1},
        "avg_age": {"$avg": "$age"}
    }}
]

result = await User.aggregate(pipeline).to_list()

# Using Beanie aggregation
from beanie.odm.queries.aggregation import AggregationQuery

stats = await User.find(User.is_active == True).aggregate([
    {"$group": {
        "_id": "$department",
        "count": {"$sum": 1}
    }}
]).to_list()

インデックス

from beanie import Document, Indexed
from pymongo import IndexModel, ASCENDING, DESCENDING, TEXT

class Product(Document):
    # Single field index
    sku: Indexed(str, unique=True)

    # Compound index defined in Settings
    name: str
    category: str
    price: float
    description: str

    class Settings:
        name = "products"
        indexes = [
            # Compound index
            IndexModel(
                [("category", ASCENDING), ("price", DESCENDING)],
                name="category_price_idx"
            ),
            # Text index
            IndexModel(
                [("name", TEXT), ("description", TEXT)],
                name="search_idx"
            ),
            # TTL index
            IndexModel(
                [("expires_at", ASCENDING)],
                expireAfterSeconds=0,
                name="ttl_idx"
            )
        ]

トランザクション

from beanie import Document
from motor.motor_asyncio import AsyncIOMotorClientSession

async def transfer_funds(
    from_account_id: str,
    to_account_id: str,
    amount: float,
    session: AsyncIOMotorClientSession
):
    async with await session.start_transaction():
        from_account = await Account.get(from_account_id, session=session)
        to_account = await Account.get(to_account_id, session=session)

        if from_account.balance < amount:
            raise ValueError("Insufficient funds")

        await from_account.set(
            {Account.balance: from_account.balance - amount},
            session=session
        )
        await to_account.set(
            {Account.balance: to_account.balance + amount},
            session=session
        )

サービスレイヤーパターン

from typing import List, Optional
from beanie import PydanticObjectId

class UserService:
    async def get_by_id(self, user_id: str) -> Optional[User]:
        return await User.get(PydanticObjectId(user_id))

    async def get_by_email(self, email: str) -> Optional[User]:
        return await User.find_one(User.email == email)

    async def get_all(
        self,
        skip: int = 0,
        limit: int = 100,
        is_active: Optional[bool] = None
    ) -> List[User]:
        query = User.find_all()
        if is_active is not None:
            query = User.find(User.is_active == is_active)
        return await query.skip(skip).limit(limit).to_list()

    async def create(self, data: UserCreate) -> User:
        user = User(**data.model_dump())
        await user.insert()
        return user

    async def update(self, user_id: str, data: UserUpdate) -> Optional[User]:
        user = await self.get_by_id(user_id)
        if not user:
            return None
        update_data = data.model_dump(exclude_unset=True)
        update_data["updated_at"] = datetime.utcnow()
        await user.set(update_data)
        return user

追加リソース

リファレンスファイル

詳細なパターンと移行ガイドについては、以下を参照してください。

  • references/migrations.md - データベース移行戦略
  • references/performance.md - クエリ最適化のヒント
  • references/relationships.md - Link と BackLink のパターン
サンプルファイル

examples/ にある動作するサンプル:

  • examples/document_models.py - ドキュメント定義一式
  • examples/aggregations.py - 集約パイプラインのサンプル
  • examples/service.py - サービスレイヤーの実装

コメント