Добавить объект, связывающий два существующих объекта - PullRequest
2 голосов
/ 09 января 2012

У меня есть две таблицы, testInstance и bugzilla, связанные с третьей таблицей bzCheck, например:

class Instance(Base):
    __tablename__ = "testInstance"

    id = Column(Integer, primary_key=True)

    bz_checks = relation(BZCheck, backref="instance")

class BZCheck(Base):
    __tablename__ = "bzCheck"

    instance_id = Column(Integer, ForeignKey("testInstance.id"), primary_key=True)
    bz_id = Column(Integer, ForeignKey("bugzilla.id"), primary_key=True)
    status = Column(String, nullable=False)

    bug = relation(Bugzilla, backref="checks")

class Bugzilla(Base):
    __tablename__ = "bugzilla"

    id = Column(Integer, primary_key=True)

Бэкэнд - это сервер postgresql;Я использую SQLalchemy 0.5

Если я создаю объекты Instance, Bugzilla и BZCheck, затем делаю

bzcheck.bug = bugzilla
instance.bz_checks.append(bzcheck)

, а затем добавляю и фиксирую их;все в порядке.

Но теперь давайте предположим, что у меня есть существующий экземпляр и существующий bugzilla, и я хочу связать их:

instance = session.query(Instance).filter(Instance.id == 31).one()
bugzilla = session.query(Bugzilla).filter(Bugzilla.id == 19876).one()
check = BZCheck(status="OK")
check.bug = bugzilla
instance.bz_checks.append(check)

Сбой:

In [6]: instance.bz_checks.append(check)
2012-01-09 18:43:50,713 INFO sqlalchemy.engine.base.Engine.0x...3bd0 select nextval('"bzCheck_instance_id_seq"')
2012-01-09 18:43:50,713 INFO sqlalchemy.engine.base.Engine.0x...3bd0 None
2012-01-09 18:43:50,713 INFO sqlalchemy.engine.base.Engine.0x...3bd0 ROLLBACK

Он пытается получить новый идентификатор из несуществующей последовательности вместо использования внешнего ключа "testInstance.id" ... Я не понимаю, почему.У меня были похожие проблемы при попытке изменить объекты после их фиксации;Я должен был пропустить что-то фундаментальное, но что?

1 Ответ

2 голосов
/ 09 января 2012

часть, которую вы пропускаете здесь, является трассировкой стека.Всегда смотрите на трассировку стека - здесь важно то, что она автоматически очищается, получая доступ instance.bz_checks:

Traceback (most recent call last):
  File "test.py", line 44, in <module>
    instance.bz_checks.append(check)
  File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/attributes.py", line 168, in __get__
    return self.impl.get(instance_state(instance),dict_)
  File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/attributes.py", line 453, in get
    value = self.callable_(state, passive)
  File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/strategies.py", line 563, in _load_for_state
    result = q.all()
  File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/query.py", line 1983, in all
    return list(self)
  File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/query.py", line 2092, in __iter__
    self.session._autoflush()
  File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/session.py", line 973, in _autoflush
    self.flush()
  File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/session.py", line 1547, in flush
    self._flush(objects)
  File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/session.py", line 1616, in _flush
    flush_context.execute()
  File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/unitofwork.py", line 328, in execute
    rec.execute(self)
  File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/unitofwork.py", line 472, in execute
    uow
  File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/mapper.py", line 2291, in _save_obj
    execute(statement, params)
  File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/engine/base.py", line 1405, in execute
    params)
  File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/engine/base.py", line 1538, in _execute_clauseelement
    compiled_sql, distilled_params
  File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/engine/base.py", line 1646, in _execute_context
    context)
  File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/engine/base.py", line 1639, in _execute_context
    context)
  File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/engine/default.py", line 330, in do_execute
    cursor.execute(statement, parameters)
sqlalchemy.exc.IntegrityError: (IntegrityError) null value in column "instance_id" violates not-null constraint
 'INSERT INTO "bzCheck" (bz_id, status) VALUES (%(bz_id)s, %(status)s) RETURNING "bzCheck".instance_id' {'status': 'OK', 'bz_id': 19876}

, вы можете видеть это, потому что строка кода:

instance.bz_checks.append(check)

затем автоматическая промывка:

self.session._autoflush()
File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/session.py", line 973, in _autoflush

Три решения:

a.временно отключите автозапуск (см. http://www.sqlalchemy.org/trac/wiki/UsageRecipes/DisableAutoflush)

b. убедитесь, что объект ассоциации BZCheck всегда создается с полным состоянием, необходимым перед доступом к любым коллекциям :

BZState (bug = bugzilla, instance = instance)

(обычно это хорошая идея для объектов ассоциации - они представляют связь между двумя точками, поэтому наиболее целесообразно, чтобы они были созданы с этим состоянием)

в. Изменить правила каскада так, чтобы операция check.bug = somebug фактически не помещала check в сессию только . Вы можете сделать это с помощью cascade_backrefs, как описано в http://www.sqlalchemy.org/docs/orm/session.html#controlling-cascade-on-backrefs. (но вы должны быть на 0,6 или 0,7)

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