Migrating from SQLAlchemy
Migration from SQLAlchemy ORM to SQLAlchemy Naked¶
This guide will help you transition your codebase from SQLAlchemy's ORM to naked-sqla, an opinionated wrapper around SQLAlchemy Core designed to simplify database interactions.:
To do this, basically:
- Replace session.add(...), session.delete(...), and similar methods with explicit INSERT and DELETE queries.
- Stop mutating objects for updates and instead use direct UPDATE queries.
- Replace implicit joins via relationships with explicit joins using foreign keys.
- Stop using session.refresh(...), and instead use explicit SELECT queries.
- Remove relationship and back_populates.
Assume you have the following models defined using SQLAlchemy ORM, and you want to migrate to naked-sqla:
# models.py
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import relationship
from sqlalchemy.orm import DeclarativeBase, Mapped, MappedAsDataclass, mapped_column
class BaseSQL(MappedAsDataclass, DeclarativeBase): ...
class User(BaseSQL):
__tablename__ = 'users'
id: Mapped[int] = mapped_column(Integer, primary_key=True)
name: Mapped[str]
class Address(BaseSQL):
__tablename__ = 'addresses'
id: Mapped[int] = mapped_column(Integer, primary_key=True)
email: Mapped[str]
user_id: Mapped[int] = mapped_column(Integer, ForeignKey('users.id'))
user: Mapped[User] = relationship('User', back_populates='addresses')
1. Replacing session.add and session.delete¶
Before (Using ORM):
# app.py
new_user = User(name='Alice')
session.add(new_user)
session.flush()
# now new_user.id is populated
After (Using Naked SQLAlchemy):
# app.py
from dataclasses import asdict
new_user = User(name='Alice')
query = insert(User).values([asdict(new_user)]).returning(User)
new_user_db = engine.execute(query)
# now new_user_db.id is populated, but new_user.id is not because it's a different object
2. Replacing session.delete with DELETE query¶
Before (Using ORM):
After (Using Naked SQLAlchemy):
import sqlalchemy as sa
user = ...
query = sa.delete(users_table).where(users_table.id == user.id)
engine.execute(query)
3. Replacing implicit joins with explicit joins¶
Before (Using ORM):
# app.py
address = session.execute(select(Address).join(User).where(User.id == 1)).scalar()
user = address.user
After (Using Naked SQLAlchemy):
# app.py
query_result = session.tuples(select(Address, User).join(User).where(User.id == 1)).all()
address, user = query_result[0]
4. Replacing session.refresh with explicit SELECT query¶
Before (Using ORM):
After (Using Naked SQLAlchemy):
# app.py
old_user = ...
user = session.scalar(select(User).where(User.id == old_user.id))
# now user is up-to-date, but it's a different object from old_user
5. Removing relationship and back_populates¶
Let's redefine our model that we had at first, To migrate, you simply need to take out the relationship and back_populates;
Before (Using ORM):
# models.py
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import relationship
from sqlalchemy.orm import DeclarativeBase, Mapped, MappedAsDataclass, mapped_column
class BaseSQL(MappedAsDataclass, DeclarativeBase): ...
class User(BaseSQL):
__tablename__ = 'users'
id: Mapped[int] = mapped_column(Integer, primary_key=True)
name: Mapped[str]
class Address(BaseSQL):
__tablename__ = 'addresses'
id: Mapped[int] = mapped_column(Integer, primary_key=True)
email: Mapped[str]
user_id: Mapped[int] = mapped_column(Integer, ForeignKey('users.id'))
user: Mapped[User] = relationship('User', back_populates='addresses')
After (Using Naked SQLAlchemy):
# models.py
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import relationship
from sqlalchemy.orm import DeclarativeBase, Mapped, MappedAsDataclass, mapped_column
class BaseSQL(MappedAsDataclass, DeclarativeBase): ...
class User(BaseSQL):
__tablename__ = 'users'
id: Mapped[int] = mapped_column(Integer, primary_key=True)
name: Mapped[str]
class Address(BaseSQL):
__tablename__ = 'addresses'
id: Mapped[int] = mapped_column(Integer, primary_key=True)
email: Mapped[str]
user_id: Mapped[int] = mapped_column(Integer, ForeignKey('users.id'))