SQLAlchemy: успешная вставка, но затем возникает исключение
Я запускаю SQLAlchemy против FirebirdSQL, и когда я выполняю insert
Команда в моем проекте, SQLAlchemy вызывает исключение при возврате из выполнения по соединению. Тем не менее, insert
запрос строится и успешно выполняется. Запрос к базе данных показывает, что элементы фактически вставлены правильно.
Редактировать: я копаю в fbcore.py
модуль, и проверка значения value
а также vartype
указывает на то, что проблема, вероятно, заключается в том, SEQUENCE
Элемент, используемый для генерации идентификатора первичного ключа, возвращает его данные. vartype
является SQL_LONG
, но фактическое значение [<an integer>]
где <an integer>
значение, возвращаемое генератором последовательности, который я создал для автоматического увеличения первичного ключа (например, [14]
). Это говорит мне о том, что проблема должна быть решена путем исправления этого, хотя я не уверен, как это сделать. Генератор работает правильно в самой базе данных, но вызывает проблемы при возврате в SQLAlchemy.
Смотрите ниже мою существующую реализацию и трассировку стека для деталей.
Мой код:
class Project:
# (I've snipped project instantiation, where engine connection, table, etc. are configured)
def save_project(self, id_=None, title=None, file_name=None, file_location=None):
# Build the dictionary of values to store
values = {}
if title is not None:
values['title'] = title
if file_name is not None:
values['file_name'] = file_name
if file_location is not None:
values['file_location'] = file_location
# Simplification: I account for the case that there *is* data---skipping that here
# Execute the correct kind of statement: insert or settings_update.
if id_ is None:
statement = self.table.insert()
else:
statement = self.table.update().where(self.table.c.id == id_)
result = self.connection.execute(statement, values)
# If we inserted a row, get the new primary key. Otherwise, return
# the one specified by the user; it does not change on settings_update.
project_id = result.inserted_primary_key if result.is_insert else id_
След:
File "/Users/chris/development/quest/workspace/my_project/data/tables.py", line 350, in save_project
result = self.connection.execute(statement, values)
File "/Users/chris/.virtualenvs/my_project/lib/python3.3/site-packages/sqlalchemy/engine/base.py", line 720, in execute
return meth(self, multiparams, params)
File "/Users/chris/.virtualenvs/my_project/lib/python3.3/site-packages/sqlalchemy/sql/elements.py", line 317, in _execute_on_connection
return connection._execute_clauseelement(self, multiparams, params)
File "/Users/chris/.virtualenvs/my_project/lib/python3.3/site-packages/sqlalchemy/engine/base.py", line 817, in _execute_clauseelement
compiled_sql, distilled_params
File "/Users/chris/.virtualenvs/my_project/lib/python3.3/site-packages/sqlalchemy/engine/base.py", line 947, in _execute_context
context)
File "/Users/chris/.virtualenvs/my_project/lib/python3.3/site-packages/sqlalchemy/engine/base.py", line 1111, in _handle_dbapi_exception
util.reraise(*exc_info)
File "/Users/chris/.virtualenvs/my_project/lib/python3.3/site-packages/sqlalchemy/util/compat.py", line 168, in reraise
raise value
File "/Users/chris/.virtualenvs/my_project/lib/python3.3/site-packages/sqlalchemy/engine/base.py", line 940, in _execute_context
context)
File "/Users/chris/.virtualenvs/my_project/lib/python3.3/site-packages/sqlalchemy/dialects/firebird/kinterbasdb.py", line 106, in do_execute
cursor.execute(statement, parameters or [])
File "/Users/chris/.virtualenvs/my_project/lib/python3.3/site-packages/fdb/fbcore.py", line 3323, in execute
self._ps._execute(parameters)
File "/Users/chris/.virtualenvs/my_project/lib/python3.3/site-packages/fdb/fbcore.py", line 2991, in _execute
self.__Tuple2XSQLDA(self._in_sqlda, parameters)
File "/Users/chris/.virtualenvs/my_project/lib/python3.3/site-packages/fdb/fbcore.py", line 2782, in __Tuple2XSQLDA
sqlvar.sqlscale)
File "/Users/chris/.virtualenvs/my_project/lib/python3.3/site-packages/fdb/fbcore.py", line 2266, in _check_integer_range
if (value < vmin) or (value > vmax):
TypeError: unorderable types: list() < int()
Я еще недостаточно знаком с SQLAlchemy, чтобы понять, почему это проблема; стиль моего высказывания в значительной степени идентичен стилю в уроке. Похоже, это проблема с передачей параметров - возможно, что-то об использовании dict, а не аргументов с ключевыми словами? Но в документах нет ничего о том, как обращаться с параметрами, которые указывают на то, что у меня здесь что-то не так - это кажется правильным из того, что я там вижу.
Я также попробовал это с self.table.insert().values(values)
вместо того, чтобы передать values
срок до execute
метод, с теми же результатами (как я и ожидал).
Редактировать: я отмечаю от прочтения документации execute
в fbcore.py
что он вызывает ошибку TypeError, когда параметры, передаваемые методу, не передаются ни как список, ни как кортеж. Это изменение, которое еще не отражено в документации?
Редактировать 2: Как отмечается в комментарии, трассировка стека указывает, что он работает с драйвером kinterbasdb, хотя я явно настроил движок для работы с использованием fdb. Это также смущает меня.
2 ответа
Как и следовало ожидать, особенно когда я обнаружил, что проблема заключалась в том, что строка вставлялась как ожидалось, но затем вызывалась с UPDATE
Вскоре после этого проблема была связана с кодом. Я возвращал результат как project_id
(как вы можете видеть в приведенном выше коде), и по совершенно не связанной причине (связанной с сигналами Блинкера) метод снова вызывался с возвращенным значением project_id
, который я установил так:
project_id = result.inserted_primary_key if result.is_insert else id_
Правильная версия этой строки немного отличается:
project_id = result.inserted_primary_key[0] if result.is_insert else id_
Из документации по SQLAlchemy (выделено мое):
Вернуть первичный ключ для только что вставленной строки.
Возвращаемое значение представляет собой список скалярных значений, соответствующих списку столбцов первичного ключа в целевой таблице.
Возвращаемое значение здесь должно быть списком, потому что первичные ключи могут быть комбинацией нескольких полей в базе данных. (Это должно было быть для меня очевидно; очевидно, что я не занимался серьезной работой с базами данных более года.) Поскольку первичный ключ в этом случае представляет собой одно значение, я просто выбрал это значение и возвратил его, и проблема заключается в том, что решена.
Конечно, теперь мне нужно разобраться с проблемой сигнала Блинкера - этот метод не должен вызываться дважды - но лучше всего...
Я изучал документацию по SQL Alchemy, и мне интересно, стоит ли вам это делать:
if id_ is None:
statement = self.table.insert()
else:
statement = self.table.update().where(self.table.c.id == id_)
statement = statement.values(title=title, file_name=file_name, file_location=file_location)
result = self.connection.execute(statement)
То есть: вместо передачи словаря в исполняемую часть сделайте его частью оператора (как показано выражениями вставки).