Теперь у меня нет всех деталей, но достаточно, чтобы разобраться в вашей ситуации. Поддержка многопоточности в SQLite не очень хороша.По этой причине поведение пула SQLAlchemy по умолчанию равно SingletonThreadPool
при использовании базы данных в памяти или NullPool
при использовании файла.Последнее означает, что пул вообще отсутствует, или, другими словами, соединение всегда открывается и закрывается в соответствии с запросом.
Позиция print()
имеет значение, потому что приведенный выше вызов session.commit()
истекает все загруженное состояние базы данныхобъекты в сессии.Таким образом, чтобы напечатать список ключей, который в итоге вызывает их __repr__
, SQLAlchemy должен повторно получить состояние каждого объекта.Если вы добавите echo=True
к своему вызову к create_engine()
, это станет очевидным.
После всего этого ваш session
в take_keys_2
удерживает соединение с открытой транзакцией.Вот где она становится немного мутной: при выходе из функции session
выходит из области видимости, и это означает, что удерживаемое ею соединение в конечном итоге возвращается в пул.Но пул NullPool
, поэтому он завершает и закрывает соединение и сбрасывает его.Завершение означает откат любой открытой транзакции, и вот что терпит неудачу:
Traceback (most recent call last):
File "~/Work/sqlalchemy/lib/sqlalchemy/pool.py", line 705, in _finalize_fairy
fairy._reset(pool)
File "~/Work/sqlalchemy/lib/sqlalchemy/pool.py", line 876, in _reset
pool._dialect.do_rollback(self)
File "~/Work/sqlalchemy/lib/sqlalchemy/engine/default.py", line 457, in do_rollback
dbapi_connection.rollback()
sqlite3.ProgrammingError: SQLite objects created in a thread can only be used in that same thread.The object was created in thread id 140683561543424 and this is thread id 140683635291968
Завершение выполняется в «фиктивном» потоке во время выключения интерпретатора вместо рабочего, так как соединение было оставлено.
Если, например, вы добавляете вызов к session.rollback()
после print(keys)
:
def take_keys_2(n):
...
with dblock:
...
session.commit()
print(keys)
session.rollback()
, соединение возвращается в пул явно , и take_keys_2
работает какЧто ж.Другой вариант - использовать expire_on_commit=False
, чтобы после фиксации не требовалось никаких дополнительных запросов для печати представления объектов Key
:
def take_keys_2(n):
with dblock:
session: Session = scopedmaker(expire_on_commit=False)
...