How It Works Features Pricing Blog Error Guides
Log In Start Free Trial
FastAPI · Python

Fix MissingGreenlet: sqlalchemy.exc.MissingGreenlet: greenlet_spawn has not been called; can't call await_only() here in FastAPI

This error occurs when you use SQLAlchemy's synchronous ORM operations inside an async FastAPI endpoint with an async engine. The sync API calls await_only() but no greenlet context exists. Fix it by using AsyncSession with async-compatible query methods like session.execute(select(...)) instead of session.query(...).

Reading the Stack Trace

Traceback (most recent call last): File "/app/venv/lib/python3.11/site-packages/uvicorn/protocols/http/h11_impl.py", line 406, in run_asgi result = await app(scope, receive, send) File "/app/venv/lib/python3.11/site-packages/starlette/routing.py", line 677, in __call__ await route.handle(scope, receive, send) File "/app/venv/lib/python3.11/site-packages/fastapi/routing.py", line 234, in app raw_response = await run_endpoint_function(dependant=dependant, values=values) File "/app/src/routes/products.py", line 18, in get_products products = db.query(Product).all() File "/app/venv/lib/python3.11/site-packages/sqlalchemy/orm/session.py", line 2659, in execute result = self._execute_internal(statement, params) File "/app/venv/lib/python3.11/site-packages/sqlalchemy/engine/base.py", line 1412, in _execute_clauseelement ret = connection._execute_clauseelement( File "/app/venv/lib/python3.11/site-packages/sqlalchemy/util/_concurrency_py3k.py", line 68, in await_only raise exc.MissingGreenlet( sqlalchemy.exc.MissingGreenlet: greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place?

Here's what each line means:

Common Causes

1. Using session.query() with an async engine

The legacy session.query() API is synchronous and incompatible with create_async_engine. Use select() with session.execute() instead.

from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession

engine = create_async_engine(DATABASE_URL)

@app.get("/products")
async def get_products(db: AsyncSession = Depends(get_db)):
    products = db.query(Product).all()  # Sync API, won't work
    return products

2. Accessing lazy-loaded relationships in async context

Lazy loading triggers a synchronous database call when accessing a relationship attribute in an async session.

@app.get("/orders/{order_id}")
async def get_order(order_id: int, db: AsyncSession = Depends(get_db)):
    result = await db.execute(select(Order).where(Order.id == order_id))
    order = result.scalar_one()
    items = order.items  # Lazy load triggers sync IO

3. Mixing sync and async session factories

The application creates an async engine but uses a synchronous sessionmaker, causing mismatched context.

from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy.orm import sessionmaker

engine = create_async_engine(DATABASE_URL)
SessionLocal = sessionmaker(bind=engine)  # Wrong: sync sessionmaker with async engine

The Fix

Use async_sessionmaker with class_=AsyncSession for proper async database access. Replace the synchronous db.query() with await db.execute(select(...)) which is compatible with the async engine. Use result.scalars().all() to extract model instances from the result.

Before (broken)
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker

engine = create_async_engine(DATABASE_URL)
SessionLocal = sessionmaker(bind=engine)

@app.get("/products")
async def get_products(db: AsyncSession = Depends(get_db)):
    products = db.query(Product).all()
    return products
After (fixed)
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from sqlalchemy import select

engine = create_async_engine(DATABASE_URL, pool_pre_ping=True)
async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)

async def get_db():
    async with async_session() as session:
        yield session

@app.get("/products")
async def get_products(db: AsyncSession = Depends(get_db)):
    result = await db.execute(select(Product))
    products = result.scalars().all()
    return products

Testing the Fix

import pytest
import pytest_asyncio
from httpx import AsyncClient, ASGITransport
from app.main import app


@pytest.mark.asyncio
async def test_get_products_returns_list():
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as ac:
        response = await ac.get("/products")
    assert response.status_code == 200
    assert isinstance(response.json(), list)


@pytest.mark.asyncio
async def test_get_products_returns_correct_fields():
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as ac:
        response = await ac.get("/products")
    if response.json():
        product = response.json()[0]
        assert "id" in product
        assert "name" in product


@pytest.mark.asyncio
async def test_async_session_closes_properly():
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as ac:
        for _ in range(10):
            response = await ac.get("/products")
            assert response.status_code == 200

Run your tests:

pytest tests/test_async_db.py -v --asyncio-mode=auto

Pushing Through CI/CD

git checkout -b fix/fastapi-async-database,git add src/database.py src/routes/products.py tests/test_async_db.py,git commit -m "fix: use async_sessionmaker and select() for async database access",git push origin fix/fastapi-async-database

Your CI config should look something like this:

name: CI
on:
  pull_request:
    branches: [main]
jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_DB: testdb
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
          cache: 'pip'
      - run: pip install -r requirements.txt
      - run: pytest --tb=short -q --asyncio-mode=auto
        env:
          DATABASE_URL: postgresql+asyncpg://postgres:postgres@localhost:5432/testdb

The Full Manual Process: 18 Steps

Here's every step you just went through to fix this one bug:

  1. Notice the error alert or see it in your monitoring tool
  2. Open the error dashboard and read the stack trace
  3. Identify the file and line number from the stack trace
  4. Open your IDE and navigate to the file
  5. Read the surrounding code to understand context
  6. Reproduce the error locally
  7. Identify the root cause
  8. Write the fix
  9. Run the test suite locally
  10. Fix any failing tests
  11. Write new tests covering the edge case
  12. Run the full test suite again
  13. Create a new git branch
  14. Commit and push your changes
  15. Open a pull request
  16. Wait for code review
  17. Merge and deploy to production
  18. Monitor production to confirm the error is resolved

Total time: 30-60 minutes. For one bug.

Or Let bugstack Fix It in Under 2 minutes

Every step above? bugstack does it automatically.

Step 1: Install the SDK

pip install bugstack

Step 2: Initialize

import bugstack

bugstack.init(api_key=os.environ["BUGSTACK_API_KEY"])

Step 3: There is no step 3.

bugstack handles everything from here:

  1. Captures the stack trace and request context
  2. Pulls the relevant source files from your GitHub repo
  3. Analyzes the error and understands the code context
  4. Generates a minimal, verified fix
  5. Runs your existing test suite
  6. Pushes through your CI/CD pipeline
  7. Deploys to production (or opens a PR for review)

Time from error to fix deployed: Under 2 minutes.

Human involvement: zero.

Try bugstack Free →

No credit card. 5-minute setup. Cancel anytime.

Deploying the Fix (Manual Path)

  1. Run the async test suite locally to confirm database queries work with the async engine.
  2. Open a pull request with the async session and query migration.
  3. Wait for CI checks including async database tests to pass.
  4. Have a teammate review and approve the PR.
  5. Merge to main and monitor query performance in staging.

Frequently Asked Questions

BugStack runs async integration tests against a real database, verifies all queries use the async-compatible API, and confirms no MissingGreenlet errors occur under concurrent load.

BugStack never pushes directly to production. Every fix goes through a pull request with full CI checks, so your team can review the async database migration before merging.

Not necessarily. If your endpoints are defined with def instead of async def, FastAPI runs them in a thread pool where synchronous SQLAlchemy works fine. Async SQLAlchemy is only needed for async def endpoints.

Use selectinload() or joinedload() in your select statements to eagerly load relationships, since lazy loading is not supported in async sessions.