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()But when you run it, your app crashes with:
Plaintext
AttributeError: 'AsyncSession' object has no attribute 'query'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 AttributeErroruser = await session.query(User).filter(User.id == user_id).first()The New Way (SQLAlchemy 2.0):
-
Import
selectfromsqlalchemy. -
Build the query object.
-
Pass it to
await session.execute().
Python
from sqlalchemy import select
# 1. Build the statementstmt = select(User).where(User.id == user_id)
# 2. Execute and get resultresult = await session.execute(stmt)
# 3. Extract the actual object (scalars)user = result.scalar_one_or_none()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.
Working on something similar?
If you're building backend or AI systems and want a second set of senior eyes, let's talk.