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.