惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

P
Proofpoint News Feed
V
V2EX
WordPress大学
WordPress大学
Google DeepMind News
Google DeepMind News
Martin Fowler
Martin Fowler
小众软件
小众软件
Blog — PlanetScale
Blog — PlanetScale
月光博客
月光博客
The Cloudflare Blog
T
Tailwind CSS Blog
H
Help Net Security
腾讯CDC
爱范儿
爱范儿
人人都是产品经理
人人都是产品经理
H
Hackread – Cybersecurity News, Data Breaches, AI and More
The GitHub Blog
The GitHub Blog
Microsoft Security Blog
Microsoft Security Blog
Stack Overflow Blog
Stack Overflow Blog
D
DataBreaches.Net
C
Check Point Blog
量子位
酷 壳 – CoolShell
酷 壳 – CoolShell
美团技术团队
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Managing MongoDB Session with Benie 2.0
Serhii Chorn · 2026-05-19 · via DEV Community

Hi DevTo. It's my first post. So be gentle with me :)

The problem

I have been using Beanie ODM and MongoDB in my work for a long time.

It's a great tool, but managing sessions and transactions across the service layer was problematic for me.

My service layers need to know about the session object to pass it in Beanie ODM model methods to bind it with the session.

It looks like this


class SomeService:


    def run_business_logic(db_session: AsyncIOMotorClientSession) -> Product:
        chocolate = Category(name="Chocolate", description="A preparation of roasted and ground cacao seeds.")

        tonybar = Product(name="Tony's", price=5.95, category=chocolate)
        await tonybar.save(session=db_session) # set session
        return tonybar


important_service = SomeService()

@router.post("")
async def do_some_service_logic(
    db_session: AsyncIOMotorClientSession = Depends(get_session),
):
    async with await session.start_transaction(): # Start transaction
        return await important_service.run_business_logic(
            db_session=db_session, # We need to pass the session on the service layer to work with Beanie
        )

Enter fullscreen mode Exit fullscreen mode

It's making me feel frustrated, because now I need to pass the session object to my service.

Game changer

But things changed after Beanie released version 2.0, and Beanie moved from Motor in favour of the PyMongo Async client.

So pymongo gives the option to bind all calls to MongoDB with the method .bind()

So basically, pymongo can create a session, and can bind to this session all calls to MongoDB.

Example from the documentation.

async with client.start_session() as s:
    async with s.bind():
        # session=s is passed implicitly
        await client.db.collection.insert_one({"x": 1})

Enter fullscreen mode Exit fullscreen mode

For more details, here is how it works inside the pymongo client:


_SESSION: ContextVar[Optional[AsyncClientSession]] = ContextVar("SESSION", default=None)


class _AsyncBoundSessionContext:
    """Context manager returned by AsyncClientSession.bind() that manages bound state."""

    def __init__(self, session: AsyncClientSession, end_session: bool) -> None:
        self._session = session
        self._session_token: Optional[Token[AsyncClientSession]] = None
        self._end_session = end_session

    async def __aenter__(self) -> AsyncClientSession:
        self._session_token = _SESSION.set(self._session)  # type: ignore[assignment]
        return self._session

    async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
        if self._session_token:
            _SESSION.reset(self._session_token)  # type: ignore[arg-type]
            self._session_token = None
        if self._end_session:
            await self._session.end_session()


class AsyncClientSession:

    ...
    def bind(self, end_session: bool = True) -> _AsyncBoundSessionContext:
        return _AsyncBoundSessionContext(self, end_session)

Enter fullscreen mode Exit fullscreen mode

So PyMongo will set the session object in _SESSION ContextVar and on any call to the database will check if there is a session, if there is a session, it will use it.

Because PyMongo sits underneath Beanie, this works transparently. I don't need to change Beanie itself at all. Just create a session and call .bind() method, and everything else will be handled by the pymongo client.

Because _SESSION is ContextVar, this means that the session is task-safe for us.

For a transaction, we can use a session that is set in _SESSION context.

Now we can wrap all this logic in Python decorators to reuse it:


from pymongo.asynchronous.client_session import _SESSION, AsyncClientSession


def get_client() -> "AsyncMongoClient":
    # Function that returns the current Mongo client
    ...


def auto_session(**session_kwargs) -> Callable:
    """ Decorator that sets session automatically"""

    def decorator(f: Callable) -> Callable:
        @wraps(f)
        async def wrapper(*args, **kwargs):
            client = get_client()  # Return MongoClientAsync
            async with client.start_session(**session_kwargs) as s:
                async with s.bind():
                    return await f(*args, **kwargs)

        return wrapper

    return decorator


def wrap_in_transaction(**transaction_kwargs) -> Callable:
    """ Decorator that sets transaction automatically with bind session"""

    def decorator(f: Callable) -> Callable:
        @wraps(f)
        async def wrapper(*args, **kwargs):
            # Take session from context directly from pymongo. asynchronous.client_session
            db_session: AsyncClientSession | None = _SESSION.get()
            if not db_session:
                raise Exception("Session not initialized, start session with using .bind() method")

            async with await db_session.start_transaction(**transaction_kwargs):
                return await f(*args, **kwargs)

        return wrapper

    return decorator

Enter fullscreen mode Exit fullscreen mode

And we can use decorators on the endpoint level.

# The Service layer is now clean. No 'session' argument needed!
class SomeService:
    async def run_business_logic(self) -> Product:
        chocolate = Category(name="Chocolate", description="...")
        tonybar = Product(name="Tony's", price=5.95, category=chocolate)
        # Beanie finds the session automatically via PyMongo's ContextVar
        await tonybar.save() 
        return tonybar

# The Controller remains clean and focused on HTTP
@router.post("")
@auto_session() # Automatically starts and binds the session
@wrap_in_transaction() # Wraps the execution in a transaction if the service raise error, Mongo will rollback data
async def do_some_service_logic():
    return await important_service.run_business_logic()

Enter fullscreen mode Exit fullscreen mode

Now our services do not know anything about the session, which means more clean code for us:

  • Sessions are task-safe, each call of the endpoint will get its own session
  • We do not bind transactions to a session, we just use it from the context of the pymongo client
  • Business logic (Our services) stays focused on the domain, not knowing about how to handle database sessions.