Ограничение Flask SQLAlchemy NOT NULL не удалось для первичного ключа

Я пытаюсь создать базу данных в SQLite с двумя таблицами: одна для списка аэропортов, а другая для списка поездок между парами этих аэропортов. Я установил это как самореферентное отношение «многие ко многим»:

class Trips(db.Model):

    __tablename__ = 'trips'

    id = db.Column(db.Integer, primary_key=True)
    airport_from = db.Column(db.Integer, db.ForeignKey('airport.id'))
    airport_to = db.Column(db.Integer, db.ForeignKey('airport.id'))
    price = db.Column(db.Float)
    date = db.Column(db.Date)

class Airport(db.Model):

    __tablename__ = 'airport'

    id = db.Column(db.Integer, primary_key=True)
    iata = db.Column(db.String(8), index=True, unique=True)
    name = db.Column(db.String(120), index=True, unique=True)
    city = db.Column(db.String(120))
    region = db.Column(db.String(120))
    country = db.Column(db.String(120))

    flying_from = db.relationship('Trips', backref='end', primaryjoin=(id==Trips.airport_to))
    flying_to = db.relationship('Trips', backref='start', primaryjoin=(id==Trips.airport_from))

    def __repr__(self):
        return '<Airport: {0}; IATA: {1}>'.format(self.name, self.iata)

Когда я открываю свою оболочку Python и импортирую эти модели, сеанс SQLAlchemy добавляет объекты Airport и выполняет коммит, но когда я делаю что-то вроде:

>>> t = models.Trips(airport_from=3, airport_to=4, price=230.0)
>>> db.session.add(t)
>>> db.session.commit()

Это дает мне эту трассировку:

Traceback (most recent call last):
  File "/Users/heli/nomad/flask/lib/python3.4/site-packages/sqlalchemy/engine/base.py", line 1139, in _execute_context
context)
  File "/Users/heli/nomad/flask/lib/python3.4/site-packages/sqlalchemy/engine/default.py", line 450, in do_execute
cursor.execute(statement, parameters)
sqlite3.IntegrityError: NOT NULL constraint failed: trips.id

Вышеупомянутое исключение было прямой причиной следующего исключения:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/heli/nomad/flask/lib/python3.4/site-packages/sqlalchemy/orm/scoping.py", line 150, in do
    return getattr(self.registry(), name)(*args, **kwargs)
  File "/Users/heli/nomad/flask/lib/python3.4/site-packages/sqlalchemy/orm/session.py", line 813, in commit
    self.transaction.commit()
  File "/Users/heli/nomad/flask/lib/python3.4/site-packages/sqlalchemy/orm/session.py", line 392, in commit
    self._prepare_impl()
  File "/Users/heli/nomad/flask/lib/python3.4/site-packages/sqlalchemy/orm/session.py", line 372, in _prepare_impl
    self.session.flush()
  File "/Users/heli/nomad/flask/lib/python3.4/site-packages/sqlalchemy/orm/session.py", line 2027, in flush
    self._flush(objects)
  File "/Users/heli/nomad/flask/lib/python3.4/site-packages/sqlalchemy/orm/session.py", line 2145, in _flush
    transaction.rollback(_capture_exception=True)
  File "/Users/heli/nomad/flask/lib/python3.4/site-packages/sqlalchemy/util/langhelpers.py", line 60, in __exit__
    compat.reraise(exc_type, exc_value, exc_tb)
  File "/Users/heli/nomad/flask/lib/python3.4/site-packages/sqlalchemy/util/compat.py", line 183, in reraise
    raise value
  File "/Users/heli/nomad/flask/lib/python3.4/site-packages/sqlalchemy/orm/session.py", line 2109, in _flush
    flush_context.execute()
  File "/Users/heli/nomad/flask/lib/python3.4/site-packages/sqlalchemy/orm/unitofwork.py", line 373, in execute
    rec.execute(self)
  File "/Users/heli/nomad/flask/lib/python3.4/site-packages/sqlalchemy/orm/unitofwork.py", line 532, in execute
    uow
  File "/Users/heli/nomad/flask/lib/python3.4/site-packages/sqlalchemy/orm/persistence.py", line 174, in save_obj
    mapper, table, insert)
  File "/Users/heli/nomad/flask/lib/python3.4/site-packages/sqlalchemy/orm/persistence.py", line 800, in _emit_insert_statements
    execute(statement, params)
  File "/Users/heli/nomad/flask/lib/python3.4/site-packages/sqlalchemy/engine/base.py", line 914, in execute
    return meth(self, multiparams, params)
  File "/Users/heli/nomad/flask/lib/python3.4/site-packages/sqlalchemy/sql/elements.py", line 323, in _execute_on_connection
    return connection._execute_clauseelement(self, multiparams, params)
  File "/Users/heli/nomad/flask/lib/python3.4/site-packages/sqlalchemy/engine/base.py", line 1010, in _execute_clauseelement
    compiled_sql, distilled_params
  File "/Users/heli/nomad/flask/lib/python3.4/site-packages/sqlalchemy/engine/base.py", line 1146, in _execute_context
    context)
  File "/Users/heli/nomad/flask/lib/python3.4/site-packages/sqlalchemy/engine/base.py", line 1341, in _handle_dbapi_exception
    exc_info
  File "/Users/heli/nomad/flask/lib/python3.4/site-packages/sqlalchemy/util/compat.py", line 189, in raise_from_cause
    reraise(type(exception), exception, tb=exc_tb, cause=exc_value)
  File "/Users/heli/nomad/flask/lib/python3.4/site-packages/sqlalchemy/util/compat.py", line 182, in reraise
    raise value.with_traceback(tb)
  File "/Users/heli/nomad/flask/lib/python3.4/site-packages/sqlalchemy/engine/base.py", line 1139, in _execute_context
    context)
  File "/Users/heli/nomad/flask/lib/python3.4/site-packages/sqlalchemy/engine/default.py", line 450, in do_execute
    cursor.execute(statement, parameters)
sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) NOT NULL constraint failed: trips.id [SQL: 'INSERT INTO trips (airport_from, airport_to, price, date) VALUES (?, ?, ?, ?)'] [parameters: (3, 4, 230.0, None)]

Ключевой частью, кажется, является нижняя строка:

sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) NOT NULL constraint failed: trips.id [SQL: 'INSERT INTO trips (airport_from, airport_to, price, date) VALUES (?, ?, ?, ?)'] [parameters: (3, 4, 230.0, None)]

Похоже, что из-за того, что я не указал значение параметра id, он отказывается добавлять объект. Но я думал, что этот id будет автоматически добавляться и увеличиваться, как это произошло с параметром id объектов Airport. Что мне здесь не хватает?


person HLH    schedule 03.01.2016    source источник


Ответы (3)


Добавьте явный autoincrement=True в определение класса Trips:

id = db.Column(db.Integer, primary_key=True, autoincrement=True)

Если таблица создается без явного АВТОИНКРЕМЕНТА, вам необходимо передать Trips.id=NULL, чтобы увеличить ее, см. https://www.sqlite.org/faq.html#q1

person MOCKBA    schedule 03.01.2016
comment
Спасибо Д, это сработало! Любая идея относительно того, почему я должен делать это для поездок, но не когда я добавляю объект аэропорта? т.е. когда я создаю объект Airport без явного идентификатора, я могу добавить его и зафиксировать сеанс без каких-либо проблем. - person HLH; 04.01.2016
comment
Хороший вопрос, Хе) Возможно ли, что вы передаете Airport.id=NULL, когда создаете новый объект Airport или таблица Airport уже определена в SQLite с атрибутом AUTOINCREMENT? - person MOCKBA; 05.01.2016
comment
Я перепроверил, и я не сделал ни того, ни другого, хотя могу ошибаться. Впрочем, это не проблема, пока все работает. - person HLH; 05.01.2016
comment
Я наткнулся на ту же проблему без очевидной причины. Я думаю, что впервые это произошло, когда я переупорядочил определения таблиц в исходном файле. Не знаю. @HeLi Может быть, ты забыл принять ответ;)? - person Dominik George; 05.08.2016

В версии: SQLAlchemy 1.2.7 Flask-SQLAlchemy 2.3.2 autoincrement = True не является обязательным параметром, но будьте осторожны с определением вашего первичного ключа, поскольку вы определили столбец «id» в качестве первичного ключа, вы не должны определять другой столбец в качестве первичного ключа, если это не требуется, в противном случае вы должны установить значение столбца id самостоятельно при вставке записи.

person Xb74Dkjb    schedule 30.04.2018

У меня была такая же проблема в таблице сопоставления классов, используемой как много-много с дополнительными полями атрибутов. Причина появления ошибки заключалась не в том, что автоинкремент не был явно определен для первичного ключа, а в том, что в столбцах external_key также было значение primary_key = True. Отношения прекрасно работают без дополнительных основных ключей, установленных в модели.

person Synectome    schedule 01.08.2020