Реагируйте на UniqueKeyViolation в SQLAlchemy

1

У меня есть 2 таблицы в SQLAlchemy (с использованием declerative_base()), которые объединены через отдельную таблицу

# no comment on the wired naming....
site_word = Table('word_site', Base.metadata,
    Column('site_id', Integer, ForeignKey('sites.site_id'), nullable = False, primary_key = True),
    Column('word_id', Integer, ForeignKey('site_words.word_id'), nullable = False, primary_key = True))

Отображение слов

class Word(Base):
   # snip
   word =  Column('word', Text, nullable = False, unique = True)
   sites = relationship('Site', secondary = site_word, back_populates = 'words')

Отображение сайтов

class Site(Base):
   # snip
   words = relationship('Word', secondary = site_word, back_populates = 'sites')

Вставка таких работ, как и ожидалось, вставляет уже существующие слова, конечно, из-за unique в Word.word.

Я попытался использовать событие before_insert, чтобы проверить, существует ли элемент уже.

event.listen(Word, 'before_insert', some_event)

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

Я мог бы написать триггер или процедуру для базы данных, но не хочу перемещать слишком много логики в базу данных (не знаю, возможно ли это, так как в то время Site не будет известен). Я мог бы удалить ограничение для word -column, но я все еще не могу понять, как получить доступ к информации (на другом конце соединения), чтобы создать запись в таблице соединений, но не в таблице Word.

Я ищу способ, чтобы создать запись в site_word и Site только таблице.

обновление 1:
Я мог бы связать событие с Site но я не вижу возможности получить информацию о сайте, который будет вставлен. Есть ли возможность сохранить сайт и впоследствии создать связь?

Теги:
sqlalchemy
unique-constraint

2 ответа

1
Лучший ответ

Мне удалось это сделать. Ответ Марка Геммилла заставил меня думать в правильном направлении.

Мне нужно прослушивать атрибут AttributeEvent вместо MapperEvent.

event.listen(Site.words, 'append', test_event, retval = True)

Тогда я могу сделать что-то вроде:

def test_event(target, value, initiator):
    try:
        return session.query(Word).filter(Word.word == value.word.lower()).one()
    except sqlalchemy.orm.exc.NoResultFound:
        return value 

Это либо возвращает существующее слово из таблицы, либо просто возвращает сопоставленный объект, который необходимо сохранить.

1

Трудно увидеть проблему без примера того, как вы работаете с вашими объектами. Я так понимаю, вы пытаетесь сделать что-то вроде этого:

site = Site()
site.words.append(Word('Someword'))
session.add(site)
session.commit()

Это, конечно же, вызовет IntegrityError если "somord" уже существует. Чтобы обойти это, вам нужно сначала запросить базу данных для слова, прежде чем добавлять его на сайт:

def get_unique_word(session, word):
    try:
        return session.query(Word).filter(Word.word==word).one()
    except sqlalchemy.orm.exc.NoResultFound:
        return Word(word)

site = Site()
site.words.append(get_unique_word(session, 'someword'))
session.add(site)
session.commit()

Если это полностью не работает, вам нужно будет описать свою проблему более подробно.

  • 0
    Ваше предложение не является ответом, но привело меня в правильное русло решения. Спасибо за ваши усилия. Не знаю, почему я игнорировал другие события из API;)

Ещё вопросы

Сообщество Overcoder
Наверх
Меню