DEV Community

Cover image for Fixing sqlalchemy.exc.MissingGreenlet in FastAPI and Async SQLAlchemy 2.0
Ayush Kumar
Ayush Kumar

Posted on

Fixing sqlalchemy.exc.MissingGreenlet in FastAPI and Async SQLAlchemy 2.0

The Problem

You just migrated your FastAPI application to Async SQLAlchemy 2.0. You run your code, everything looks fine, and then bam. Your API crashes with this scary error message:

sqlalchemy.exc.MissingGreenlet: greenlet_spawn has not been called; can't call await_only() function.

If you are seeing this, you are not alone. This is the #1 pitfall developers encounter when switching from synchronous to asynchronous database calls.

The Cause

This error happens when you try to access a Relationship attribute (like user.posts) that was not loaded in the original query.

In the old synchronous world, SQLAlchemy would "lazily" load this data for you. It would silently pause your code, run a quick SQL query to get the posts, and then continue.

In the Async world, this is illegal. SQLAlchemy cannot pause the event loop to run a synchronous "lazy load" query. Since it can't run the query, it crashes.

The Solution (Eager Loading)

To fix this, you must tell SQLAlchemy to fetch the related data upfront using "Eager Loading" strategies like selectinload or joinedload.

The Broken Code (Lazy Loading):

Python

# This query only fetches the User, not their posts
query = select(User).where(User.id == 1)
result = await session.execute(query)
user = result.scalar_one()

# CRASH HAPPENS HERE 💥
# SQLAlchemy tries to fetch 'posts' synchronously, but can't.
print(user.posts)

The Fix (Eager Loading): Import selectinload and add it to your query options.
Enter fullscreen mode Exit fullscreen mode
Python
`
from sqlalchemy.orm import selectinload`

# Tell SQLAlchemy to fetch 'posts' immediately
query = select(User).options(selectinload(User.posts)).where(User.id == 1)

result = await session.execute(query)
user = result.scalar_one()

# Works perfectly! The data is already there.
print(user.posts)
Enter fullscreen mode Exit fullscreen mode

Which Option Should You Use?

selectinload: Best for One-to-Many relationships (e.g., User -> Posts). It runs two separate queries (one for users, one for posts) which is often faster for lists.

joinedload: Best for Many-to-One relationships (e.g., Post -> Author). It runs a single query with a SQL JOIN.

My Personal Tip

If you want to prevent this error from ever happening again, you can disable lazy loading entirely in your model configuration. This forces you to be explicit about your queries, which is better for performance anyway.

Python

# In your model definition
class User(Base):
    __tablename__ = "users"
    # Raise an error if you forget to load data
    posts = relationship("Post", lazy="raise_on_sql")

Enter fullscreen mode Exit fullscreen mode

Next Steps

Now that you have fixed your relationships, make sure your entire AsyncEngine is configured correctly. Check out my Complete Guide to Async FastAPI & SQLAlchemy 2.0 for a full production setup.

Top comments (0)