DEV Community

Cover image for AsyncSession Has No Attribute query in SQLAlchemy 2.0 (FastAPI Fix)
Ayush Kumar
Ayush Kumar

Posted on • Originally published at logiclooptech.dev

AsyncSession Has No Attribute query in SQLAlchemy 2.0 (FastAPI Fix)

If you recently upgraded to SQLAlchemy 2.0 and suddenly saw AttributeError: 'AsyncSession' object has no attribute 'query', nothing is actually broken.

This error appears because SQLAlchemy 2.0 removed legacy query patterns entirely and async sessions were never meant to support them in the first place.

In FastAPI projects, this usually shows up during async migrations where old ORM habits silently clash with the new execution model.

In this article, we’ll explain why AsyncSession.query() no longer exists, how SQLAlchemy 2.0 expects queries to be written, and how to fix this correctly in async FastAPI applications.

The Problem

You are following an older FastAPI tutorial, or maybe you are migrating a legacy Flask app to AsyncIO. You write a standard database query like this:

Python

# The "Old Way"
users = await session.query(User).filter_by(id=1).first()
Enter fullscreen mode Exit fullscreen mode

But when you run it, your app crashes with:

Plaintext

AttributeError: 'AsyncSession' object has no attribute 'query'
Enter fullscreen mode Exit fullscreen mode

This is confusing because session.query() has been the standard way to write queries in SQLAlchemy for a decade. Why is it gone?


The Root Cause

In SQLAlchemy 2.0, the classic session.query(...) pattern is considered "Legacy."

While the synchronous Session still supports it for backward compatibility, the modern AsyncSession strictly enforces the new 2.0 style syntax. It does not have a .query() method at all.

To fix this, you need to stop using the "Method Chaining" style (e.g., session.query().filter()) and switch to the "Statement" style.


The Solution

You must construct a select statement first, and then execute it.

The Old Way (Broken in Async):

Python

# This throws AttributeError
user = await session.query(User).filter(User.id == user_id).first()
Enter fullscreen mode Exit fullscreen mode

The New Way (SQLAlchemy 2.0):

  1. Import select from sqlalchemy.

  2. Build the query object.

  3. Pass it to await session.execute().

Python

from sqlalchemy import select

# 1. Build the statement
stmt = select(User).where(User.id == user_id)

# 2. Execute and get result
result = await session.execute(stmt)

# 3. Extract the actual object (scalars)
user = result.scalar_one_or_none()
Enter fullscreen mode Exit fullscreen mode

Common Patterns Cheat Sheet

Here is how to translate your old queries to the new syntax:

1. Fetch All Records

  • Old: session.query(User).all()

  • New:

    Python

    result = await session.execute(select(User))
    users = result.scalars().all()
    

2. Fetch One Record (by ID)

  • Old: session.query(User).get(1)

  • New:

    Python

    user = await session.get(User, 1)
    

3. Filtering

  • Old: session.query(User).filter(User.active == True)

  • New:

    Python

    stmt = select(User).where(User.active == True)
    result = await session.execute(stmt)
    

This is part of a broader shift in how async architectures should be structured in FastAPI + SQLAlchemy 2.0.

Why scalars() ?

You will notice result.scalars() is used often. By default, session.execute() returns "Rows" (tuples), like (User(id=1),). Calling .scalars() strips that tuple wrapper and gives you the clean ORM objects directly.


More Async Gotchas

If you fixed this error but are now seeing sqlalchemy.exc.MissingGreenlet, you are likely running into Lazy Loading issues. →Read my guide on fixing the MissingGreenlet error here.

Top comments (0)