🐛 Типичные ошибки блока SQLAlchemy
⚡ Топ-3 ошибки блока
- Забыть
session.commit()после add/delete. - Использовать
having()вместоfilter()до группировки. - Обращаться к атрибутам объекта после
session.close()(DetachedInstanceError).
1. Забыть session.commit()
Добавление объекта через session.add() лишь переводит его в состояние Pending. Без commit() изменения не попадут в БД.
# Неправильно
session.add(User(name="Alice"))
# Изменения НЕ сохранены!
# Правильно
session.add(User(name="Alice"))
session.commit()
2. DetachedInstanceError после закрытия сессии
После session.close() объекты переходят в состояние Detached. Обращение к «ленивым» атрибутам (связи с lazy='select') вызовет ошибку.
# Неправильно
user = session.query(User).get(1)
session.close()
print(user.addresses) # DetachedInstanceError!
# Правильно — загрузить данные до закрытия
user = session.query(User).get(1)
addresses = user.addresses # загрузка в рамках сессии
session.close()
print(addresses) # OK
3. having() вместо filter() до группировки
having() работает только с агрегатными функциями — после group_by(). Для фильтрации по обычным полям используйте filter().
# Неправильно
session.query(User).having(User.age > 25).all()
# SQLAlchemy может выполнить это, но это неправильная семантика
# Правильно
session.query(User).filter(User.age > 25).all()
# having() — только после group_by для агрегатов
session.query(User.age, func.count(User.id)).group_by(
User.age
).having(func.count(User.id) > 1).all()
4. Забыть Base.metadata.create_all(engine)
Без вызова create_all() таблицы в базе данных не создадутся, и любые запросы вернут ошибку.
# Обязательно вызвать после определения моделей
Base.metadata.create_all(engine)
5. Путать .one() и .first()
.one() выбрасывает исключение при 0 или 2+ результатах. .first() просто возвращает None при отсутствии результатов. Выбирайте в зависимости от ожиданий.
# .one() — ожидается ровно один результат
user = session.query(User).filter(User.email == "alice@example.com").one()
# MultipleResultsFound или NoResultFound при несоответствии
# .first() — ожидается ноль или один результат
user = session.query(User).filter(User.name == "Alice").first()
# None если не найден
6. Импорт declarative_base из неверного модуля
В SQLAlchemy 1.x модуль был sqlalchemy.ext.declarative, в 2.x переехал в sqlalchemy.orm.
# Из лекции (устарело, но совместимо)
from sqlalchemy.ext.declarative import declarative_base
# Правильно для актуальных версий
from sqlalchemy.orm import declarative_base
7. Не закрыть соединение при ошибке
При ошибке в транзакции нужно делать rollback(), иначе соединение окажется в неопределённом состоянии.
try:
session.add(User(name="Alice"))
session.commit()
except Exception as e:
session.rollback()
raise
finally:
session.close()
8. Двойная инициализация relationship без back_populates
При создании двунаправленной связи без back_populates (или backref) SQLAlchemy не знает о второй стороне связи, что приводит к неожиданному поведению.
# Неправильно (только одна сторона)
class User(Base):
addresses = relationship("Address")
class Address(Base):
user = relationship("User") # независимая связь, не синхронизирована
# Правильно
class User(Base):
addresses = relationship("Address", back_populates="user")
class Address(Base):
user = relationship("User", back_populates="addresses")