SQLAlchemy MissingGreenlet: greenlet_spawn has not been called (tested)
sqlalchemy.exc.MissingGreenlet: greenlet_spawn has not been called; can't call await_() here means an AsyncSession needed a database query you did not await: a lazy relationship, or an attribute expired by commit(). Use expire_on_commit=False, selectinload(), or await obj.awaitable_attrs.rel.
All blocks import this common.py (async engine on aiosqlite, one author with one book):
from sqlalchemy import ForeignKey, select
from sqlalchemy.ext.asyncio import AsyncAttrs, create_async_engine, async_sessionmaker
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
class Base(AsyncAttrs, DeclarativeBase): pass
class Author(Base):
__tablename__ = "authors"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str]
books: Mapped[list["Book"]] = relationship()
class Book(Base):
__tablename__ = "books"
id: Mapped[int] = mapped_column(primary_key=True)
author_id: Mapped[int] = mapped_column(ForeignKey("authors.id"))
title: Mapped[str]
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
async def setup(**kw):
async with engine.begin() as c:
await c.run_sync(Base.metadata.create_all)
Session = async_sessionmaker(engine, **kw)
async with Session() as s:
s.add(Author(name="Ada", books=[Book(title="Notes")])); await s.commit()
return Session
1. The error: a lazy relationship inside async code
import asyncio
from common import *
async def main():
Session = await setup()
async with Session() as s:
a = (await s.scalars(select(Author))).first()
print(a.name)
print(a.books) # lazy load = implicit sync IO inside async code
asyncio.run(main())
Output: Ada, then a 181-line traceback ending in:
sqlalchemy.exc.StatementError: (sqlalchemy.exc.MissingGreenlet) greenlet_spawn has not been called; can't call await_() here. Was IO attempted in an unexpected place?
[SQL: SELECT books.id, books.author_id, books.title
FROM books
WHERE ? = books.author_id]
[parameters: [{'%(140460893975088 param)s': 1}]]
(Background on this error at: https://sqlalche.me/e/21/xd2s)
<sys>:0: RuntimeWarning: coroutine 'Connection.cursor' was never awaited
With AsyncSession, every database round trip must be awaited. Touching a.books is a plain attribute access, but the relationship is lazy, so SQLAlchemy tries to run SELECT ... FROM books synchronously. It cannot, and raises MissingGreenlet (wrapped in StatementError). The [SQL: ...] line tells you which relationship or column triggered it.
2. Plain columns after commit fail the same way
import asyncio
from common import *
async def main():
Session = await setup() # expire_on_commit=True (the default)
async with Session() as s:
a = (await s.scalars(select(Author))).first()
await s.commit()
try:
print(a.name) # expired by commit -> refresh needs IO
except Exception as e:
print(type(e).__name__ + ":", str(e)[:110])
asyncio.run(main())
Output:
StatementError: (sqlalchemy.exc.MissingGreenlet) greenlet_spawn has not been called; can't call await_() here. Was IO attempte
<sys>:0: RuntimeWarning: coroutine 'Connection.cursor' was never awaited
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
The default expire_on_commit=True expires every attribute at commit(), so reading a.name afterwards needs a refresh query, which is again implicit IO. FastAPI handlers that return an object after committing hit this constantly.
3. Fix: eager-load, expire_on_commit=False, or awaitable_attrs
import asyncio
from sqlalchemy.orm import selectinload
from common import *
async def main():
Session = await setup(expire_on_commit=False)
async with Session() as s:
a = (await s.scalars(select(Author).options(selectinload(Author.books)))).first()
await s.commit()
print(a.name, [b.title for b in a.books]) # eager load + no expiry
b = (await s.scalars(select(Author))).first()
print(await b.awaitable_attrs.books) # or load lazily, but awaited
asyncio.run(main())
Output:
Ada ['Notes']
[<common.Book object at 0x7f38ac378190>]
Three tools, usually combined: async_sessionmaker(engine, expire_on_commit=False) (recommended for async), selectinload() for relationships you will read, and, when you really want lazy loading, await obj.awaitable_attrs.books, which needs the AsyncAttrs mixin on your Base (as in common.py; without it you get AttributeError: ... has no attribute 'awaitable_attrs'). For code that is easier written synchronously, await session.run_sync(fn) runs fn where lazy loads are allowed.
Related: SQLAlchemy DetachedInstanceError (the synchronous cousin) · SQLAlchemy no such table · asyncio TimeoutError and CancelledError.
Frequently asked questions
What does greenlet_spawn has not been called mean?
SQLAlchemy's async layer had to run a query from a plain attribute access (lazy load or refresh after commit), where it cannot await. Load the data explicitly instead.
How do I fix MissingGreenlet after commit?
Create the session factory with async_sessionmaker(engine, expire_on_commit=False) so committed objects keep their loaded values.
How do I lazy load a relationship with AsyncSession?
Add the AsyncAttrs mixin to Base and use await obj.awaitable_attrs.relationship_name, or eager-load with selectinload().
Written with AI assistance. Measured on 2026-10-23 with SQLAlchemy 2.1.6, aiosqlite 0.22.1 and Python 3.14.7 (python:3-alpine in Docker). Each block was run verbatim and the output pasted unedited (block 1 shows the end of the traceback). PlainTables is our extension; Python and SQLAlchemy are free.