Guides
SQLAlchemy Migration Guides
Exact-problem pages for SQLAlchemy 1.4 to 2.0 breakages.
Problem pages
Exact-problem pages for SQLAlchemy 1.4 to 2.0 breakages.
How to replace session.query(...).get(...) in SQLAlchemy 2.0Fix Query.get migrations and move identity lookups onto Session.get(...).What to do with session.query(...) during a SQLAlchemy 2.0 migrationSeparate the easy Query cleanup from the broader Query-to-select refactor work.How to fix engine.execute(...) removal in SQLAlchemy 2.0Fix engine.execute removals and OptionEngine errors without pretending the replacement is always trivial.How to clean up joinedload_all(...) during a SQLAlchemy 2.0 migrationReplace joinedload_all(...) with mapped-attribute loader options and bucket the hard cases honestly.How to replace select([..]) list syntax in SQLAlchemy 2.0Drop the legacy select([..]) wrapper and move to direct select(...) calls.How to replace string join paths during a SQLAlchemy 2.0 migrationReplace simple join("addresses") and outerjoin("orders") calls with mapped attributes when the root entity is obvious.How to replace string loader options in SQLAlchemy 2.0Replace joinedload("addresses") and similar string loader options with mapped attributes when the relationship root is obvious.How to move sqlalchemy.ext.declarative imports to sqlalchemy.ormReplace legacy declarative_base and declared_attr imports from sqlalchemy.ext.declarative.How to replace insert(..., values=...) in SQLAlchemy 2.0Move legacy insert constructor kwargs onto the statement object with .values(...).How to replace update(..., whereclause=..., values=...) in SQLAlchemy 2.0Move legacy update constructor kwargs onto the statement object with .where(...).values(...).How to replace delete(..., whereclause=...) in SQLAlchemy 2.0Move legacy delete constructor kwargs onto the statement object with .where(...).What to do about Query.from_self() removal in SQLAlchemy 2.0Treat Query.from_self() as a manual-review migration instead of pretending there is one safe automatic replacement.Why multi-hop string joins are a manual-review case in SQLAlchemy 2.0Understand why join("orders", "items") belongs in manual review instead of a guessed codemod.How to replace Query.as_scalar() with scalar_subquery() in SQLAlchemy 2.0Migrate legacy Query.subquery().as_scalar() calls to the scalar_subquery() method.How to wrap raw SQL strings in text() for SQLAlchemy 2.0Raw SQL strings now need explicit text() wrapping in SQLAlchemy 2.0 execute calls.How to adapt session.execute() result handling in SQLAlchemy 2.0Session.execute() in 2.0 returns Result objects, not lists. Use .scalars() or .all() for ORM rows.How to replace backref strings with back_populates in SQLAlchemy 2.0The string backref pattern still works but explicit back_populates is the modern approach.Fix 'OptionEngine' object has no attribute 'execute' in SQLAlchemy 2.0Fix the common OptionEngine execute error by moving execution onto a Connection or Session.Fix 'Engine' object has no attribute 'execute' in SQLAlchemy 2.0Resolve Engine.execute attribute errors without guessing transaction behavior.Fix LegacyAPIWarning: Query.get() is legacy in SQLAlchemy 2.0Replace Query.get warnings with Session.get in the safe primary-key lookup subset.Fix SQLAlchemy legacy select([..]) warningsDrop the old select list wrapper and use direct select(...) arguments.Fix joinedload_all is not defined in SQLAlchemy 2.0Replace joinedload_all usage with chained joinedload calls when the path is obvious.SQLAlchemy 2.0 migration triage checklistSort SQLAlchemy 2.0 migration work into safe codemod, manual review, and staged refactor buckets.Manual SQLAlchemy 2.0 migration vs codemodDecide when a SQLAlchemy 2.0 migration should use a deterministic codemod and when it should stay manual.