SQLAlchemy DetachedInstanceError с обычным атрибутом (не отношение) - PullRequest
42 голосов
/ 14 июня 2010

Я только начал использовать SQLAlchemy и получил DetachedInstanceError и не могу найти много информации об этом нигде. Я использую экземпляр вне сеанса, поэтому вполне естественно, что SQLAlchemy не может загрузить любые отношения, если они еще не загружены, однако атрибут, к которому я обращаюсь, не является отношением, фактически этот объект вообще не имеет отношений. Я нашел такие решения, как готовая загрузка, но я не могу применить это, потому что это не отношение. Я даже пытался «прикоснуться» к этому атрибуту перед закрытием сеанса, но он все еще не предотвращает исключение. Что может быть причиной этого исключения для нереляционного свойства, даже после того, как оно было успешно получено ранее? Любая помощь в устранении этой проблемы приветствуется. Тем временем я постараюсь получить воспроизводимый автономный сценарий и обновить его здесь.

Обновление: это фактическое сообщение об исключении с несколькими стеками:

  File "/home/hari/bin/lib/python2.6/site-packages/SQLAlchemy-0.6.1-py2.6.egg/sqlalchemy/orm/attributes.py", line 159, in __get__
    return self.impl.get(instance_state(instance), instance_dict(instance))
  File "/home/hari/bin/lib/python2.6/site-packages/SQLAlchemy-0.6.1-py2.6.egg/sqlalchemy/orm/attributes.py", line 377, in get
    value = callable_(passive=passive)
  File "/home/hari/bin/lib/python2.6/site-packages/SQLAlchemy-0.6.1-py2.6.egg/sqlalchemy/orm/state.py", line 280, in __call__
    self.manager.deferred_scalar_loader(self, toload)
  File "/home/hari/bin/lib/python2.6/site-packages/SQLAlchemy-0.6.1-py2.6.egg/sqlalchemy/orm/mapper.py", line 2323, in _load_scalar_attributes
    (state_str(state)))
DetachedInstanceError: Instance <ReportingJob at 0xa41cd8c> is not bound to a Session; attribute refresh operation cannot proceed

Частичная модель выглядит так:

metadata = MetaData()
ModelBase = declarative_base(metadata=metadata)

class ReportingJob(ModelBase):
    __tablename__ = 'reporting_job'

    job_id         = Column(BigInteger, Sequence('job_id_sequence'), primary_key=True)
    client_id      = Column(BigInteger, nullable=True)

И поле client_id является причиной этого исключения с использованием, как показано ниже:

Запрос:

    jobs = session \
            .query(ReportingJob) \
            .filter(ReportingJob.job_id == job_id) \
            .all()
    if jobs:
        # FIXME(Hari): Workaround for the attribute getting lazy-loaded.
        jobs[0].client_id
        return jobs[0]

Это то, что позже вызывает исключение из области видимости сеанса:

        msg = msg + ", client_id: %s" % job.client_id

Ответы [ 5 ]

59 голосов
/ 14 июня 2010

Я нашел основную причину, пытаясь сузить код, вызвавший исключение. Я поместил один и тот же код доступа к атрибуту в разные места после закрытия сеанса и обнаружил, что он точно не вызывает проблем сразу после закрытия сеанса запроса. Оказывается, проблема начинает появляться после закрытия нового сеанса, который открывается для обновления объекта. Как только я понял, что состояние объекта непригодно после закрытия сеанса, я смог найти этот поток , который обсуждал эту же проблему. Два решения, которые выходят из потока:

  • Оставить сеанс открытым (что очевидно)
  • Укажите expire_on_commit=False до sessionmaker().

Третий вариант - вручную установить expire_on_commit на False в сеансе после его создания, что-то вроде: session.expire_on_commit = False. Я убедился, что это решает мою проблему.

9 голосов
/ 08 апреля 2013

Мы получали похожие ошибки, даже если expire_on_commit установлено на False. В конце концов это было вызвано двумя sessionmaker, которые оба привыкли делать сессии в разных запросах. Я не очень понимаю, что происходит, но если вы видите это исключение с expire_on_commit=False, убедитесь, что у вас нет двух sessionmaker инициализированных.

1 голос
/ 14 апреля 2018

У меня была похожая проблема с DetachedInstanceError: Instance <> is not bound to a Session;

Ситуация была довольно простой, я передаю сеанс и запись для обновления в свою функцию, и она объединяет запись и фиксирует ее в базе данных. В первом примере я получил ошибку, так как мне было лень и я думал, что могу просто вернуть объединенный объект, чтобы моя рабочая запись была обновлена ​​(то есть ее значение is_modified было бы ложным). Он вернул обновленную запись, и is_modified теперь был ложным, но последующее использование показало ошибку. Я думаю, что это было усугублено из-за связанных дочерних записей, но не совсем уверен в этом.

        def EditStaff(self, session, record):
            try:
                    r = session.merge(record)
                    session.commit()
                    return r
            except:
                    return False

После долгих поисков и чтения сессий и т. Д. Я понял, что, поскольку я захватил экземпляр r перед фиксацией и вернул его, когда эта же запись была отправлена ​​обратно в эту функцию для другого редактирования / фиксации, он потерял сеанс.

Таким образом, чтобы исправить это, я просто запрашиваю в базе данных только что обновленную запись и возвращаю ее, чтобы сохранить ее в сеансе и пометить ее значение is_modified обратно в false.

        def EditStaff(self, session, record):
            try:
                    session.merge(record)
                    session.commit()
                    r = self.GetStaff(session, record)
                    return r
            except:
                    return False

Установка expire_on_commit=False также позволила избежать ошибки, как упомянуто выше, но я не думаю, что это на самом деле устраняет ошибку и может привести ко многим другим проблемам IMO.

0 голосов
/ 06 марта 2019

Чтобы бросить свою причину и решение в кольцо, я использую flask и flask-sqlalchemy, чтобы управлять всеми своими сессиями.Это нормально, когда я делаю что-то через сайт, но когда я делаю что-то через командную строку и скрипты, вы должны убедиться, что все, что делает флеш-у, связано с контекстом фляги.Итак, в моей ситуации мне нужно было получить вещи из базы данных (используя flask-sqlalchemy), затем отобразить их в шаблоны (используя render_template колбы), а затем отправить их по электронной почте (используя flask-mail).то, что я сделал, было что-то вроде

def render_obj(db_obj):
  with app.app_context():
    return render_template('template_for_my_db_obj.html', db_obj=db_obj

def get_renders():
  my_db_objs = MyDbObj.query.all()

  renders = []
  for day, _db_objs in itertools.groupby(my_db_objs, MyDbObj.get_date):
    renders.extend(list(map(render_obj, _db_obj)))

  return renders

def email_report():
  renders = get_renders()
  report = '\n'.join(renders)

  with app.app_context():
    mail.send(Message('Subject', ['me@me.com'], html=report))

(это в основном псевдокод, я делал другие вещи в разделе группировки)

И когда я бегал, ясначала пройдите _db_obj, но потом я получу ошибку при любом запуске после.

Виновник?with app.app_context().

Обычно, когда вы выходите из этого контекста , он делает несколько вещей, в том числе как-то освежает соединения db.Одна из вещей, которая вытекает из этого, это избавление от последнего сеанса, который был вокруг, который был сеансом, с которым были связаны все my_db_objs.

Есть несколько различных вариантов решений, но я пошелс вариантом,

def render_obj(db_obj):
  return render_template('template_for_my_db_obj.html', db_obj=db_obj

def get_renders():
  my_db_objs = MyDbObj.query.all()

  renders = []
  for day, _db_objs in itertools.groupby(my_db_objs, MyDbObj.get_date):
    renders.extend(list(map(render_obj, _db_obj)))

  return renders

def email_report():
  with app.app_context():
    renders = get_renders()
    report = '\n'.join(renders)

    mail.send(Message('Subject', ['me@me.com'], html=report))

Только 1 with app.app_context(), который охватывает их все.Главное, что вам нужно сделать (если у вас есть настройки, подобные моей), это убедиться, что любой используемый вами объект дБ находится «внутри» любого используемого вами app_context.Если вы сделаете то, что я сделал в первой итерации, все ваши объекты в дБ потеряют свою сессию, оканчивающуюся на DetachedInstanceError, как я.

0 голосов
/ 12 сентября 2017

Что касается меня (новичка), я сделал ошибку в отступе и закрыл сеанс внутри моего цикла, в котором я зацикливаю каждую строку, выполняю некоторую операцию и фиксирую каждый раз.

Так что для таких новичков, как я, проверьте свой код перед установкой таких вещей, как expire_on_commit=False, это может привести вас к другой ловушке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...