Claude Code for Beanie: Async MongoDB ODM — Claude Skills 360 Blog
Blog / AI / Claude Code for Beanie: Async MongoDB ODM
AI

Claude Code for Beanie: Async MongoDB ODM

Published: December 25, 2027
Read time: 5 min read
By: Claude Skills 360

Beanie is an async MongoDB ODM based on Pydantic. pip install beanie motor. Define document: from beanie import Document, Indexed; class User(Document): email: Indexed(str, unique=True); name: str. Init: await init_beanie(database=db, document_models=[User, Product]). Insert: user = await User.insert(User(email="[email protected]", name="Alice")). Insert many: await User.insert_many([...]). Find: users = await User.find(User.is_active == True).to_list(). Find one: user = await User.find_one(User.email == "[email protected]"). Get by ID: user = await User.get(id). Filter: User.find(User.age >= 18, User.role == "admin"). Sort: .sort(-User.created_at). Limit/skip: .limit(20).skip(offset). Count: await User.find(...).count(). Update: await user.set({User.name: "Bob"}). await User.find(...).update(Set({User.is_active: False})). Delete: await user.delete(). await User.find(...).delete(). Aggregate: await User.aggregate([{"$group":{"_id":"$role","count":{"$sum":1}}}]).to_list(). Link: class Order(Document): user: Link[User]. await order.fetch_link(Order.user). BackLink: class User(Document): orders: list[BackLink[Order]] = []. BulkWriter: async with BulkWriter() as bw: await User.insert(user, bulk_writer=bw). Settings override: class Settings: name="users_collection"; indexes=[...]. PydanticObjectId — Beanie’s _id type. Optional[PydanticObjectId]. Document.model_config = ConfigDict(populate_by_name=True). Indexed(str) — single field index. Indexed(str, index_type=pymongo.TEXT) — text search. @before_event(Insert, Replace) async def update_timestamp(self): self.updated_at = datetime.utcnow(). Claude Code generates Beanie Document models, aggregation pipelines, and FastAPI MongoDB CRUD services.

CLAUDE.md for Beanie

## Beanie Stack
- Version: beanie >= 1.26 | pip install beanie motor
- Init: await init_beanie(database=AsyncIOMotorDatabase, document_models=[...])
- Model: class Doc(Document): field: type = ... | Indexed(type, unique=True)
- Insert: await Doc.insert(Doc(...)) | await Doc.insert_many([...])
- Find: Doc.find(*conditions).sort().limit().skip().to_list()
- Update: await doc.set({Doc.field: val}) | Doc.find(...).update(Set({...}))
- Aggregate: Doc.aggregate([pipeline stages]).to_list()
- Links: Link[OtherDoc] + fetch_link() | BackLink for reverse references

Beanie Async MongoDB Pipeline

# app/documents.py — Beanie document model definitions
from __future__ import annotations

from datetime import datetime, timezone
from decimal import Decimal
from enum import Enum
from typing import Annotated, Optional

import pymongo
from beanie import (
    BackLink,
    BulkWriter,
    Document,
    Indexed,
    Link,
    PydanticObjectId,
    WriteRules,
    before_event,
)
from beanie.operators import In, Set
from pydantic import ConfigDict, EmailStr, Field


class UserRole(str, Enum):
    USER      = "user"
    MODERATOR = "moderator"
    ADMIN     = "admin"


class OrderStatus(str, Enum):
    PENDING   = "pending"
    PAID      = "paid"
    SHIPPED   = "shipped"
    DELIVERED = "delivered"
    CANCELLED = "cancelled"


# ── Embedded documents (not collections, just nested Pydantic models) ──────────

from pydantic import BaseModel

class Address(BaseModel):
    street:      str
    city:        str
    state:       str
    postal_code: str
    country:     str = "US"


# ── Tag document ───────────────────────────────────────────────────────────────

class Tag(Document):
    name: Indexed(str, unique=True)  # type: ignore[valid-type]
    slug: Indexed(str, unique=True)  # type: ignore[valid-type]

    class Settings:
        name = "tags"


# ── User document ──────────────────────────────────────────────────────────────

class User(Document):
    email:      Indexed(EmailStr, unique=True)  # type: ignore[valid-type]
    first_name: str
    last_name:  str
    role:       UserRole = UserRole.USER
    is_active:  bool     = True
    address:    Optional[Address] = None
    created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
    updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))

    # orders is populated via BackLink from Order.user
    # Access: await user.fetch_link(User.orders) after fetching with links=True

    model_config = ConfigDict(populate_by_name=True)

    class Settings:
        name = "users"
        indexes = [
            pymongo.IndexModel([("role", pymongo.ASCENDING), ("is_active", pymongo.ASCENDING)]),
            pymongo.IndexModel([("first_name", pymongo.TEXT), ("last_name", pymongo.TEXT)],
                               name="user_text_idx"),
        ]

    @before_event([  # type: ignore[list-item]
        "update",
    ])
    async def update_timestamp(self) -> None:
        self.updated_at = datetime.now(timezone.utc)

    @property
    def full_name(self) -> str:
        return f"{self.first_name} {self.last_name}"


# ── Product document ───────────────────────────────────────────────────────────

class Product(Document):
    name:       str
    sku:        Indexed(str, unique=True)  # type: ignore[valid-type]
    price:      float
    stock:      int   = 0
    is_active:  bool  = True
    tags:       list[Link[Tag]] = []
    created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))

    class Settings:
        name = "products"


# ── Order document with Link to User ──────────────────────────────────────────

class OrderLine(BaseModel):
    product_id:  PydanticObjectId
    product_sku: str
    quantity:    int
    unit_price:  float

    @property
    def subtotal(self) -> float:
        return self.quantity * self.unit_price


class Order(Document):
    user:       Link[User]
    lines:      list[OrderLine] = []
    status:     OrderStatus     = OrderStatus.PENDING
    total:      float           = 0.0
    notes:      Optional[str]   = None
    created_at: datetime        = Field(default_factory=lambda: datetime.now(timezone.utc))
    updated_at: datetime        = Field(default_factory=lambda: datetime.now(timezone.utc))

    class Settings:
        name = "orders"
        indexes = [
            pymongo.IndexModel([("status", pymongo.ASCENDING)]),
            pymongo.IndexModel([("created_at", pymongo.DESCENDING)]),
        ]

    @before_event(["update"])  # type: ignore[list-item]
    async def touch(self) -> None:
        self.updated_at = datetime.now(timezone.utc)
# app/services.py — async CRUD services using Beanie
from __future__ import annotations

from beanie.operators import In, Set, Inc
from app.documents import Order, OrderLine, OrderStatus, Product, Tag, User, UserRole


class UserService:

    async def create(self, email: str, first_name: str, last_name: str) -> User:
        user = User(email=email, first_name=first_name, last_name=last_name)
        return await user.insert()

    async def get_by_id(self, user_id: str) -> User | None:
        return await User.get(user_id)

    async def get_by_email(self, email: str) -> User | None:
        return await User.find_one(User.email == email)

    async def list_active(self, page: int = 1, page_size: int = 20) -> list[User]:
        return await (
            User.find(User.is_active == True)
            .sort(User.last_name)
            .skip((page - 1) * page_size)
            .limit(page_size)
            .to_list()
        )

    async def search(self, query: str) -> list[User]:
        """MongoDB text search using the text index."""
        return await User.find({"$text": {"$search": query}}).to_list()

    async def count_by_role(self) -> list[dict]:
        return await User.aggregate([
            {"$group": {"_id": "$role", "count": {"$sum": 1}}},
            {"$sort":  {"count": -1}},
        ]).to_list()

    async def deactivate(self, user_id: str) -> bool:
        result = await User.find_one(User.id == user_id)
        if result is None:
            return False
        await result.set({User.is_active: False})
        return True


class OrderService:

    async def create_order(self, user_id: str, lines_data: list[dict]) -> Order:
        """Create an order and decrement product stock."""
        user = await User.get(user_id)
        if user is None:
            raise ValueError(f"User {user_id} not found")

        product_ids = [line["product_id"] for line in lines_data]
        products    = {str(p.id): p
                       async for p in Product.find(In(Product.id, product_ids))}

        lines = []
        total = 0.0

        for line_data in lines_data:
            pid     = line_data["product_id"]
            product = products.get(str(pid))
            if product is None:
                raise ValueError(f"Product {pid} not found")
            if product.stock < line_data["quantity"]:
                raise ValueError(f"Insufficient stock for {product.sku}")

            line = OrderLine(
                product_id=product.id,
                product_sku=product.sku,
                quantity=line_data["quantity"],
                unit_price=product.price,
            )
            lines.append(line)
            total += line.subtotal

        order = Order(user=user, lines=lines, total=total)
        await order.insert()

        # Decrement stock for each product
        for line_data in lines_data:
            await Product.find_one(Product.id == line_data["product_id"]).update(
                Inc({Product.stock: -line_data["quantity"]})
            )

        return order

    async def get_orders_with_users(self, limit: int = 20) -> list[Order]:
        """Fetch orders and resolve User links."""
        return await (
            Order.find()
            .sort(-Order.created_at)
            .limit(limit)
            .fetch_links()
            .to_list()
        )

    async def revenue_summary(self) -> dict:
        """Aggregate total revenue by status."""
        pipeline = [
            {"$group": {
                "_id":   "$status",
                "total": {"$sum": "$total"},
                "count": {"$sum": 1},
            }},
            {"$sort": {"total": -1}},
        ]
        results = await Order.aggregate(pipeline).to_list()
        return {r["_id"]: {"total": r["total"], "count": r["count"]} for r in results}
# app/config.py — Beanie init and FastAPI integration
from __future__ import annotations

import os
from contextlib import asynccontextmanager

from beanie import init_beanie
from fastapi import FastAPI
from motor.motor_asyncio import AsyncIOMotorClient

from app.documents import Order, Product, Tag, User

MONGODB_URL = os.environ.get("MONGODB_URL", "mongodb://localhost:27017")
MONGODB_DB  = os.environ.get("MONGODB_DB",  "myapp")


@asynccontextmanager
async def lifespan(app: FastAPI):
    client = AsyncIOMotorClient(MONGODB_URL)
    db     = client[MONGODB_DB]
    await init_beanie(
        database=db,
        document_models=[User, Product, Tag, Order],
    )
    yield
    client.close()


def create_app() -> FastAPI:
    return FastAPI(lifespan=lifespan)

For the Motor directly alternative — Motor provides async pymongo with collection.find({}), collection.aggregate([]), and collection.update_one() but all data comes back as raw dicts requiring manual type casting, while Beanie wraps Motor with Pydantic validation so await User.find_one(User.email == email) returns a typed User instance, Indexed(str, unique=True) declares an index in Python that Beanie creates on init_beanie, and .sort(-User.created_at) uses the field directly instead of the magic string "-created_at". For the MongoEngine alternative — MongoEngine is synchronous and not compatible with asyncio/FastAPI, while Beanie’s Link[User] stores a DBRef and .fetch_links() resolves all links in a batch query, @before_event hooks run validation or timestamp updates before insert or replace operations without manual override, and BulkWriter batches multiple inserts and updates into one round-trip for high-throughput ingestion. The Claude Skills 360 bundle includes Beanie skill sets covering Document model definition, Indexed field types, init_beanie with FastAPI lifespan, insert/find/update/delete CRUD, find query chaining with sort/limit/skip, Link and BackLink relationships, aggregate pipeline, before_event hooks, BulkWriter for batch operations, and PydanticObjectId for typed IDs. Start with the free tier to try async MongoDB code generation.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free