SQLAlchemy — о проблемах с сессиями

Admin SQLAlchemy

SQLAlchemy, как и многие другие ОРМ работает с базой данных через сессии. Это отличается от работы на прямую с базой данных. Не понимания принципа работы сессий может приводить к надоедливым ошибкам.

Ошибки

sqlalchemy.orm.exc.DetachedInstanceError: Instance is not bound to a Session; attribute refresh operation cannot proceed (Background on this error at: http://sqlalche.me/e/13/bhk3)

«attribute refresh operation cannot proceed» % (state_str(state))
sqlalchemy.orm.exc.DetachedInstanceError: Instance is not bound to a Session; attribute refresh operation cannot proceed (Background on this error at: http://sqlalche.me/e/13/bhk3)

Как избежать ошибок на примере кода ниже, где все работает корректно:

def ploshadka_function(self):

    # Положим в переменную все ID пользователей (чтобы потом работать с другой сессией)
    user_ids = [x.id for x in db.session.query(User.id).distinct()]

    for x_id in user_ids:
        # Теперь мы можем работать с новой сессией
        user = db.session.query(User).filter_by(id=x_id).first()

        for setting in user.settings:
            row = vars(setting)

            if row['name'] == 'market_direction':
                self.send_market_direction(user)

            elif row['name'] == 'change_percent':
                self.send_change_percent(user, row['percent'])

            # Обновляем дату
            db.session.query(UserSetting).filter_by(name=row['name']).update({'key': value})

        # Коммитим все изменения после обработки одного пользователя
        db.session.commit()

В этом коде могло быть 2 ошибки в пересечениях сессий.

1.
Если бы мы использовали db.session.commit() внутри второго цикла for, то SQLAlchemy закрыл бы сессию, посчитав, что задача выполнена.

Вместо этого мы обновляем методом update наш экземпляр класса User, созданный выше. А в самом конце делаем commit.

2.
Но db.session.commit() все еще внутри первого цикла и мы не можем вынести его за него. Поэтому мы кладем наверху ID пользователей в переменную user_ids. ID собраны отдельно и при закрытии сессии нам не страшно, что закроется сеанс.

Например, такой код при взаимодействии выше, вызвал бы ошибки с сессиями:

users = db.session.query(User).all()
for user in users:
   settings = db.session.query(UserSetting).filter_by(user_id=user.id).all()

Это произошло бы из-за db.session.commit() внутри первого цикла for. После первого прохода в переменной users ничего бы не осталось. И во втором проходе возникла бы ошибка «Instance is not bound to a Session».

Кстати, на сайте нет рекламы. У сайта нет цели самоокупаться, но если вам пригодилась информация можете задонатить мне на чашечку кофе в макдаке. Лайкнуть страницу или просто поблагодарить. Карма вам зачтется.

Комментарии к статье “SQLAlchemy — о проблемах с сессиями

  • Константин
    23.10.2021 в 20:57

    ну… Как написано в документации SQL Alchemy — возникает ошибка из-за «ленивой загрузки». Чтобы решить проблему нужно изменить параметр сессии — установить expire_on_commit=False.

Добавить комментарий

Напишите свой комментарий, если вам есть что добавить/поправить/спросить по теме текущей статьи:
"SQLAlchemy — о проблемах с сессиями"