Сообщения об ошибках

В этом разделе приведены описания и справочная информация для распространенных сообщений об ошибках и предупреждений, выдаваемых или выдаваемых SQLAlchemy.

SQLAlchemy обычно выдает ошибки в контексте специфического для SQLAlchemy класса исключений. Подробнее об этих классах смотрите Основные исключения и Исключения ORM.

Ошибки SQLAlchemy можно условно разделить на две категории: ошибки времени программирования и ошибки времени выполнения. Ошибки времени программирования возникают в результате вызова функций или методов с неправильными аргументами, или в результате других методов, ориентированных на конфигурацию, таких как конфигурации маппера, которые не могут быть разрешены. Ошибка времени программирования обычно является немедленной и детерминированной. С другой стороны, ошибка времени выполнения представляет собой сбой, возникающий в процессе выполнения программы в ответ на некоторое условие, которое возникает произвольно, например, исчерпание соединений с базой данных или возникновение какой-либо проблемы, связанной с данными. Ошибки времени выполнения чаще всего можно увидеть в журналах работающего приложения, поскольку программа встречает эти состояния в ответ на загрузку и встречающиеся данные.

Поскольку ошибки времени выполнения не так легко воспроизвести, и они часто возникают в ответ на какие-то произвольные условия в процессе работы программы, их сложнее отлаживать, и они также влияют на программы, которые уже запущены в производство.

В этом разделе мы постараемся рассказать о некоторых наиболее распространенных ошибках времени выполнения, а также об ошибках времени программирования.

Связи и транзакции

Ограничение QueuePool размером <x> переполнение <y> достигнуто, соединение прервано, таймаут <z>

Это, возможно, самая распространенная ошибка времени выполнения, поскольку она напрямую связана с превышением рабочей нагрузки приложения над настроенным пределом, который обычно применяется почти ко всем приложениям SQLAlchemy.

Ниже кратко описано, что означает эта ошибка, начиная с самых основных моментов, с которыми большинство пользователей SQLAlchemy уже должны быть знакомы.

  • ** Объект SQLAlchemy Engine по умолчанию использует пул соединений** - Это означает, что при использовании ресурса соединения с базой данных SQL объекта Engine, а затем releases этого ресурса, само соединение с базой данных остается подключенным к базе данных и возвращается во внутреннюю очередь, где оно может быть использовано снова. Хотя может показаться, что код завершает общение с базой данных, во многих случаях приложение будет поддерживать фиксированное количество соединений с базой данных, которые сохраняются до завершения работы приложения или явного удаления пула.

  • Благодаря пулу, когда приложение использует соединение с базой данных SQL, чаще всего либо с помощью Engine.connect(), либо при выполнении запросов с помощью ORM Session, это действие не обязательно устанавливает новое соединение с базой данных в момент получения объекта соединения; вместо этого оно обращается к пулу соединений для поиска соединения, который часто извлекает существующее соединение из пула для повторного использования. Если доступных соединений нет, пул создаст новое соединение с базой данных, но только если пул не превысил заданную емкость.

  • Пул по умолчанию, используемый в большинстве случаев, называется QueuePool. Когда вы просите этот пул предоставить вам соединение, а ни одно из них не доступно, он создаст новое соединение если общее количество соединений в игре меньше настроенного значения. Это значение равно размеру пула плюс максимальное переполнение. Это означает, что если вы настроили свой движок как:

    engine = create_engine("mysql+mysqldb://u:p@host/db", pool_size=10, max_overflow=20)

    Приведенное выше Engine позволит не более 30 соединений быть в игре в любое время, не включая соединения, которые были отсоединены от движка или аннулированы. Если поступит запрос на новое соединение, а 30 соединений уже используются другими частями приложения, пул соединений будет блокироваться на определенный период времени, после чего произойдет тайминг и появится сообщение об ошибке.

    Чтобы обеспечить одновременное использование большего числа соединений, пул можно настроить с помощью параметров create_engine.pool_size и create_engine.max_overflow, передаваемых в функцию create_engine(). Тайм-аут для ожидания доступного соединения настраивается с помощью параметра create_engine.pool_timeout.

  • Пул может быть настроен на неограниченное переполнение, если установить create_engine.max_overflow на значение «-1». При такой настройке пул будет поддерживать фиксированный пул соединений, однако он никогда не будет блокироваться при запросе нового соединения; вместо этого он будет безоговорочно создавать новое соединение, если ни одно из них не доступно.

    Однако при таком способе работы, если у приложения возникнет проблема, когда оно использует все доступные ресурсы подключения, оно в конечном итоге достигнет настроенного предела доступных подключений в самой базе данных, что снова приведет к ошибке. Более серьезно, когда приложение исчерпывает базу данных подключений, оно обычно расходует большое количество ресурсов до отказа, а также может нарушить работу других приложений и механизмов состояния базы данных, которые полагаются на возможность подключения к базе данных.

    Учитывая вышесказанное, пул соединений можно рассматривать как предохранительный клапан для использования соединений, обеспечивающий критический уровень защиты от неавторизованного приложения, из-за которого вся база данных становится недоступной для всех остальных приложений. При получении этого сообщения об ошибке гораздо предпочтительнее устранить проблему, использующую слишком много соединений и/или настроить ограничения соответствующим образом, чем допускать неограниченное переполнение, которое на самом деле не решает основную проблему.

Что заставляет приложение использовать все доступные ему соединения?

  • Приложение получает слишком много одновременных запросов для выполнения работы, основанной на настроенном значении для пула - Это самая простая причина. Если у вас есть приложение, которое работает в пуле потоков, допускающем 30 одновременных потоков, с одним используемым соединением на поток, если ваш пул не настроен на одновременную проверку не менее 30 соединений, вы получите эту ошибку, когда ваше приложение получит достаточное количество одновременных запросов. Решением является повышение лимитов на пул или снижение количества одновременных потоков.

  • Приложение не возвращает соединения в пул - Это следующая наиболее распространенная причина, которая заключается в том, что приложение использует пул соединений, но программа не может release эти соединения и вместо этого оставляет их открытыми. Пул соединений, а также ORM Session имеют логику, согласно которой, когда сессия и/или объект соединения собираются в мусор, это приводит к освобождению ресурсов базового соединения, однако на это поведение нельзя полагаться в плане своевременного освобождения ресурсов.

    Чаще всего это происходит потому, что приложение использует ORM-сессии и не обращается к ним Session.close() после завершения работы с сессией. Решение состоит в том, чтобы убедиться, что сессии ORM, если используется ORM, или связанные с движком объекты Connection, если используется Core, явно закрыты по окончании выполняемой работы, либо с помощью соответствующего метода .close(), либо с помощью одного из доступных менеджеров контекста (например, оператора «with:»), чтобы правильно освободить ресурс.

  • Приложение пытается выполнить длительные транзакции - Транзакция базы данных - это очень дорогой ресурс, и ее никогда не следует оставлять без дела в ожидании какого-либо события. Если приложение ждет, пока пользователь нажмет на кнопку, или результат выйдет из давно запущенной очереди заданий, или держит открытым постоянное соединение с браузером, не держите транзакцию базы данных открытой все это время. Когда приложению необходимо работать с базой данных и взаимодействовать с событием, откройте в этот момент короткоживущую транзакцию, а затем закройте ее.

  • Приложение находится в тупике - Также распространенная причина этой ошибки и более сложная для понимания, если приложение не может завершить использование соединения либо из-за тупика со стороны приложения, либо со стороны базы данных, приложение может использовать все доступные соединения, что приводит к тому, что дополнительные запросы получают эту ошибку. Причины возникновения тупиковых ситуаций включают:

    • Использование неявной системы async, такой как gevent или eventlet, без надлежащего monkeypatching всех библиотек сокетов и драйверов, или в которой есть ошибки, не полностью покрывающие все monkeypatched методы драйверов, или, реже, когда система async используется против нагрузок на процессор, и гринлеты, использующие ресурсы базы данных, просто ждут слишком долго, чтобы их обслужить. Ни неявные, ни явные рамки программирования async обычно не нужны и не подходят для подавляющего большинства операций с реляционными базами данных; если приложение должно использовать систему async для некоторой области функциональности, лучше всего, чтобы бизнес-методы, ориентированные на базу данных, выполнялись в традиционных потоках, которые передают сообщения в асинхронную часть приложения.

    • Тупик на стороне базы данных, например, строки взаимно заблокированы

    • Ошибки потоков, например, мьютексы во взаимном тупике или обращение к уже заблокированному мьютексу в том же потоке

Помните, что альтернативой использованию пулинга является полное отключение пулинга. Смотрите раздел Реализации коммутационных пулов для получения информации об этом. Однако обратите внимание, что когда возникает это сообщение об ошибке, это всегда связано с более серьезной проблемой в самом приложении; пул просто помогает быстрее выявить проблему.

Невозможно восстановить соединение, пока не будет откачена недействительная транзакция. Пожалуйста, выполните полный откат(), прежде чем продолжить

Это состояние ошибки относится к случаю, когда Connection было аннулировано либо из-за обнаружения разъединения базы данных, либо из-за явного вызова Connection.invalidate(), но все еще присутствует транзакция, которая была инициирована либо явно методом Connection.begin(), либо из-за того, что соединение автоматически начало транзакцию, как это происходит в SQLAlchemy серии 2.x при выполнении любых SQL-запросов. Когда соединение аннулируется, любая транзакция Transaction, которая находилась в процессе, теперь находится в недействительном состоянии и должна быть явно откатана, чтобы удалить ее из Connection.

Ошибки DBAPI

API базы данных Python, или DBAPI, - это спецификация драйверов баз данных, которую можно найти по адресу Pep-249. Этот API определяет набор классов исключений, которые учитывают весь спектр режимов отказа базы данных.

SQLAlchemy не генерирует эти исключения напрямую. Вместо этого они перехватываются драйвером базы данных и оборачиваются в исключение DBAPIError, предоставляемое SQLAlchemy, однако сообщения в исключении генерируются драйвером, а не SQLAlchemy.

InterfaceError

Исключение, возникающее при ошибках, связанных с интерфейсом базы данных, а не с самой базой данных.

Эта ошибка имеет вид DBAPI Error и исходит от драйвера базы данных (DBAPI), а не от самой SQLAlchemy.

InterfaceError иногда поднимается драйверами в контексте разрыва соединения с базой данных или невозможности подключения к базе данных. Советы о том, как с этим справиться, см. в разделе Работа с разъединениями.

DatabaseError

Исключение, возникающее при ошибках, связанных с самой базой данных, а не с интерфейсом или передаваемыми данными.

Эта ошибка имеет вид DBAPI Error и исходит от драйвера базы данных (DBAPI), а не от самой SQLAlchemy.

DataError

Исключение, возникающее при ошибках, связанных с проблемами в обрабатываемых данных, таких как деление на ноль, выход числового значения за пределы диапазона и т.д.

Эта ошибка имеет вид DBAPI Error и исходит от драйвера базы данных (DBAPI), а не от самой SQLAlchemy.

OperationalError

Исключение, возникающее при ошибках, которые связаны с работой базы данных и не обязательно находятся под контролем программиста, например, происходит неожиданное отключение, имя источника данных не найдено, транзакция не может быть обработана, во время обработки произошла ошибка выделения памяти и т.д.

Эта ошибка имеет вид DBAPI Error и исходит от драйвера базы данных (DBAPI), а не от самой SQLAlchemy.

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

IntegrityError

Исключение, возникающее при нарушении реляционной целостности базы данных, например, при неудачной проверке внешнего ключа.

Эта ошибка имеет вид DBAPI Error и исходит от драйвера базы данных (DBAPI), а не от самой SQLAlchemy.

InternalError

Исключение, возникающее, когда база данных сталкивается с внутренней ошибкой, например, курсор больше не действителен, транзакция рассинхронизирована и т.д.

Эта ошибка имеет вид DBAPI Error и исходит от драйвера базы данных (DBAPI), а не от самой SQLAlchemy.

InternalError иногда поднимается драйверами в контексте разрыва соединения с базой данных или невозможности подключения к базе данных. Советы о том, как с этим справиться, см. в разделе Работа с разъединениями.

ProgrammingError

Исключение, возникающее при ошибках программирования, например, таблица не найдена или уже существует, синтаксическая ошибка в SQL-запросе, неверно указано количество параметров и т.д.

Эта ошибка имеет вид DBAPI Error и исходит от драйвера базы данных (DBAPI), а не от самой SQLAlchemy.

ProgrammingError иногда поднимается драйверами в контексте разрыва соединения с базой данных или невозможности подключения к базе данных. Советы о том, как с этим справиться, см. в разделе Работа с разъединениями.

NotSupportedError

Исключение, возникающее в случае использования метода или API базы данных, который не поддерживается базой данных, например, запрос .rollback() на соединении, которое не поддерживает транзакцию или транзакции отключены.

Эта ошибка имеет вид DBAPI Error и исходит от драйвера базы данных (DBAPI), а не от самой SQLAlchemy.

Язык выражений SQL

Объект не будет создавать ключ кэша, Последствия для производительности

SQLAlchemy начиная с версии 1.4 включает SQL compilation caching facility, который позволяет конструкциям Core и ORM SQL кэшировать их строковую форму вместе с другой структурной информацией, используемой для получения результатов из оператора, что позволяет пропустить относительно дорогостоящий процесс компиляции строки при следующем использовании структурно эквивалентной конструкции. Эта система полагается на функциональность, реализованную для всех конструкций SQL, включая такие объекты, как Column, select() и TypeEngine, чтобы создать ключ кэша, который полностью представляет их состояние в той степени, в которой оно влияет на процесс компиляции SQL.

Если предупреждения относятся к широко используемым объектам, таким как объекты Column, и показано, что они влияют на большинство используемых конструкций SQL (с использованием методов оценки, описанных в Оценка производительности кэша с помощью протоколирования) так, что кэширование обычно не включено для приложения, это негативно скажется на производительности и в некоторых случаях может привести к ухудшению производительности по сравнению с предыдущими версиями SQLAlchemy. Подробнее об этом говорится в FAQ по адресу Почему мое приложение работает медленно после обновления до версии 1.4 и/или 2.x?.

Кэширование отключается, если есть сомнения.

Кэширование основывается на возможности генерировать ключ кэша, который точно представляет полную структуру оператора в последовательном виде. Если конкретная конструкция (или тип) SQL не имеет соответствующих директив, которые позволяют ей генерировать правильный ключ кэша, то кэширование не может быть безопасно включено:

  • Ключ кэша должен представлять полную структуру: Если использование двух отдельных экземпляров этой конструкции может привести к различному отображению SQL, кэширование SQL для первого экземпляра элемента с использованием ключа кэша, который не отражает различий между первым и вторым элементами, приведет к тому, что для второго экземпляра будет кэширован и отображен некорректный SQL.

  • Ключ кэша должен быть последовательным: Если конструкция представляет состояние, которое изменяется каждый раз, например, литеральное значение, создавая уникальный SQL для каждого экземпляра, эту конструкцию также небезопасно кэшировать, поскольку повторное использование конструкции быстро заполнит кэш утверждений уникальными строками SQL, которые, скорее всего, не будут использоваться снова, что сводит на нет цель кэша.

По двум вышеуказанным причинам система кэширования SQLAlchemy очень консервативна при принятии решения о кэшировании SQL, соответствующего объекту.

Атрибуты утверждения для кэширования

Предупреждение выдается на основании приведенных ниже критериев. Подробнее о каждом из них см. в разделе Почему мое приложение работает медленно после обновления до версии 1.4 и/или 2.x?.

  • Сам Dialect (т.е. модуль, указанный первой частью URL, который мы передаем в create_engine(), например postgresql+psycopg2://), должен указывать на то, что он был рассмотрен и протестирован для корректной поддержки кэширования, на что указывает атрибут Dialect.supports_statement_cache, установленный в True. При использовании диалектов сторонних разработчиков, проконсультируйтесь с сопровождающими диалекта, чтобы они могли следить за steps to ensure caching may be enabled в своем диалекте и опубликовать новый выпуск.

  • Сторонние или определенные пользователем типы, которые наследуются от TypeDecorator или UserDefinedType, должны включать атрибут ExternalType.cache_ok в свое определение, в том числе для всех производных подклассов, следуя рекомендациям, описанным в docstring для ExternalType.cache_ok. Как и раньше, если эти типы данных импортируются из библиотек сторонних производителей, проконсультируйтесь с сопровождающими этой библиотеки, чтобы они могли внести необходимые изменения в свою библиотеку и опубликовать новый выпуск.

  • Сторонние или определенные пользователем конструкции SQL, которые являются подклассами таких классов, как ClauseElement, Column, Insert и т.д., включая простые подклассы, а также те, которые предназначены для работы с Пользовательские SQL-конструкции и расширение компиляции, обычно должны включать атрибут HasCacheKey.inherit_cache, установленный в True или False в зависимости от дизайна конструкции, следуя рекомендациям, описанным в Включение поддержки кэширования для пользовательских конструкций.

Компилятор StrSQLCompiler не может вывести элемент типа <тип элемента>

Эта ошибка обычно возникает при попытке структурировать конструкцию SQL-выражения, которая включает элементы, не являющиеся частью компиляции по умолчанию; в этом случае ошибка будет относиться к классу StrSQLCompiler. В менее распространенных случаях она также может возникнуть, когда неправильный тип SQL-выражения используется с определенным типом бэкенда базы данных; в этих случаях будут названы другие типы классов компилятора SQL, такие как SQLCompiler или sqlalchemy.dialects.postgresql.PGCompiler. Приведенное ниже руководство в большей степени относится к случаю использования «стрингизации», но описывает и общие предпосылки.

Обычно конструкцию Core SQL или объект ORM Query можно строчить напрямую, например, когда мы используем print():

>>> from sqlalchemy import column
>>> print(column("x") == 5)
{printsql}x = :x_1

Когда приведенное выше выражение SQL строится, используется класс компилятора StrSQLCompiler, который представляет собой специальный компилятор операторов, вызываемый, когда конструкция строится без какой-либо информации, специфичной для диалекта.

Однако существует множество конструкций, специфичных для определенного диалекта базы данных, для которых StrSQLCompiler не знает, как превратить в строку, например, конструкция PostgreSQL «insert on conflict»:

>>> from sqlalchemy.dialects.postgresql import insert
>>> from sqlalchemy import table, column
>>> my_table = table("my_table", column("x"), column("y"))
>>> insert_stmt = insert(my_table).values(x="foo")
>>> insert_stmt = insert_stmt.on_conflict_do_nothing(index_elements=["y"])
>>> print(insert_stmt)
Traceback (most recent call last):

...

sqlalchemy.exc.UnsupportedCompilationError:
Compiler <sqlalchemy.sql.compiler.StrSQLCompiler object at 0x7f04fc17e320>
can't render element of type
<class 'sqlalchemy.dialects.postgresql.dml.OnConflictDoNothing'>

Чтобы строчить конструкции, характерные для конкретного бэкенда, необходимо использовать метод ClauseElement.compile(), передавая либо Engine, либо Dialect объект, который вызовет нужный компилятор. Ниже мы используем диалект PostgreSQL:

>>> from sqlalchemy.dialects import postgresql
>>> print(insert_stmt.compile(dialect=postgresql.dialect()))
{printsql}INSERT INTO my_table (x) VALUES (%(x)s) ON CONFLICT (y) DO NOTHING

Для объекта ORM Query к утверждению можно получить доступ с помощью аксессора Query.statement:

statement = query.statement
print(statement.compile(dialect=postgresql.dialect()))

Дополнительную информацию о прямой структуризации / компиляции элементов SQL см. по ссылке FAQ ниже.

TypeError: <оператор> не поддерживается между экземплярами „ColumnProperty“ и <что-то>

Это часто происходит при попытке использовать объект column_property() или deferred() в контексте выражения SQL, обычно в декларативных выражениях типа:

class Bar(Base):
    __tablename__ = "bar"

    id = Column(Integer, primary_key=True)
    cprop = deferred(Column(Integer))

    __table_args__ = (CheckConstraint(cprop > 5),)

Выше, атрибут cprop используется в строке до того, как он был отображен, однако этот атрибут cprop не является Column, это ColumnProperty, который является промежуточным объектом и поэтому не имеет полной функциональности ни объекта Column, ни объекта InstrumentedAttribute, которые будут отображены на класс Bar после завершения декларативного процесса.

Хотя у ColumnProperty есть метод __clause_element__(), что позволяет ему работать в некоторых контекстах, ориентированных на столбцы, он не может работать в контексте открытого сравнения, как показано выше, поскольку у него нет метода Python __eq__(), который позволил бы ему интерпретировать сравнение с числом «5» как выражение SQL, а не как обычное сравнение Python.

Решением является прямой доступ к Column с помощью атрибута ColumnProperty.expression:

class Bar(Base):
    __tablename__ = "bar"

    id = Column(Integer, primary_key=True)
    cprop = deferred(Column(Integer))

    __table_args__ = (CheckConstraint(cprop.expression > 5),)

Требуется значение для параметра привязки <x> (в группе параметров <y>)

Эта ошибка возникает, когда оператор использует bindparam() либо неявно, либо явно и не предоставляет значения при выполнении оператора:

stmt = select(table.c.column).where(table.c.id == bindparam("my_param"))

result = conn.execute(stmt)

Выше не было указано значение для параметра «my_param». Правильным подходом является предоставление значения:

result = conn.execute(stmt, my_param=12)

Если сообщение имеет вид «требуется значение для параметра привязки <x> в группе параметров <y>», оно относится к стилю выполнения «executemany». В этом случае оператор обычно представляет собой INSERT, UPDATE или DELETE и передается список параметров. В этом формате оператор может генерироваться динамически, чтобы включить позиции параметров для каждого параметра, указанного в списке аргументов, где он будет использовать первый набор параметров, чтобы определить, какими они должны быть.

Например, приведенный ниже оператор рассчитывается на основе первого набора параметров, чтобы потребовать параметры, «a», «b» и «c» - эти имена определяют окончательный строковый формат оператора, который будет использоваться для каждого набора параметров в списке. Поскольку вторая запись не содержит «b», генерируется такая ошибка:

m = MetaData()
t = Table("t", m, Column("a", Integer), Column("b", Integer), Column("c", Integer))

e.execute(
    t.insert(),
    [
        {"a": 1, "b": 2, "c": 3},
        {"a": 2, "c": 4},
        {"a": 3, "b": 4, "c": 5},
    ],
)
sqlalchemy.exc.StatementError: (sqlalchemy.exc.InvalidRequestError)
A value is required for bind parameter 'b', in parameter group 1
[SQL: u'INSERT INTO t (a, b, c) VALUES (?, ?, ?)']
[parameters: [{'a': 1, 'c': 3, 'b': 2}, {'a': 2, 'c': 4}, {'a': 3, 'c': 5, 'b': 4}]]

Поскольку требуется «b», передайте его как None, чтобы INSERT мог продолжаться:

e.execute(
    t.insert(),
    [
        {"a": 1, "b": 2, "c": 3},
        {"a": 2, "b": None, "c": 4},
        {"a": 3, "b": 4, "c": 5},
    ],
)

Ожидался пункт FROM, а получился Select. Чтобы создать предложение FROM, используйте метод .subquery()

Это относится к изменению, внесенному в SQLAlchemy 1.4, когда оператор SELECT, сгенерированный такой функцией, как select(), а также включающий такие вещи, как союзы и текстовые выражения SELECT, больше не считаются объектами FromClause и не могут быть помещены непосредственно в предложение FROM другого оператора SELECT без предварительного обертывания в Subquery. Это серьезное концептуальное изменение в Core, и полное обоснование обсуждается в Оператор SELECT больше не считается неявным предложением FROM.

Приведем пример:

m = MetaData()
t = Table("t", m, Column("a", Integer), Column("b", Integer), Column("c", Integer))
stmt = select(t)

Выше stmt представляет собой оператор SELECT. Ошибка возникает, когда мы хотим использовать stmt непосредственно как предложение FROM в другом SELECT, например, если мы попытаемся выбрать из него:

new_stmt_1 = select(stmt)

Или если бы мы хотели использовать его в предложении FROM, например, в JOIN:

new_stmt_2 = select(some_table).select_from(some_table.join(stmt))

В предыдущих версиях SQLAlchemy при использовании SELECT внутри другого SELECT получался безымянный подзапрос, заключенный в круглые скобки. В большинстве случаев такая форма SQL не очень полезна, поскольку такие базы данных, как MySQL и PostgreSQL, требуют, чтобы подзапросы в предложениях FROM имели именованные псевдонимы, что означает использование метода SelectBase.alias() или, начиная с версии 1.4, метода SelectBase.subquery(). В других базах данных подзапрос должен иметь имя, чтобы устранить любую двусмысленность в будущих ссылках на имена столбцов внутри подзапроса.

Помимо вышеуказанных практических причин, существует множество других, ориентированных на SQLAlchemy, причин, по которым вносится это изменение. Поэтому правильная форма двух вышеприведенных утверждений требует использования SelectBase.subquery():

subq = stmt.subquery()

new_stmt_1 = select(subq)

new_stmt_2 = select(some_table).select_from(some_table.join(subq))

Для элемента raw clauseelement автоматически создается псевдоним

Добавлено в версии 1.4.26.

Это предупреждение об устаревании относится к очень старому и, вероятно, не очень известному шаблону, который применяется к унаследованному методу Query.join(), а также к методу 2.0 style Select.join(), где присоединение может быть указано в терминах relationship(), но целью является Table или другой Core selectable, с которым сопоставлен класс, а не ORM-сущность, такая как сопоставленный класс или aliased() конструкция:

a1 = Address.__table__

q = (
    s.query(User)
    .join(a1, User.addresses)
    .filter(Address.email_address == "ed@foo.com")
    .all()
)

Приведенный выше шаблон также позволяет произвольно выбирать, например, объект Core Join или Alias, однако автоматическая адаптация этого элемента не предусмотрена, поэтому на элемент Core нужно будет ссылаться напрямую:

a1 = Address.__table__.alias()

q = (
    s.query(User)
    .join(a1, User.addresses)
    .filter(a1.c.email_address == "ed@foo.com")
    .all()
)

Правильным способом указания цели присоединения всегда является использование самого сопоставленного класса или объекта aliased, в последнем случае используется модификатор PropComparator.of_type() для установки псевдонима:

# normal join to relationship entity
q = s.query(User).join(User.addresses).filter(Address.email_address == "ed@foo.com")

# name Address target explicitly, not necessary but legal
q = (
    s.query(User)
    .join(Address, User.addresses)
    .filter(Address.email_address == "ed@foo.com")
)

Присоединитесь к псевдониму:

from sqlalchemy.orm import aliased

a1 = aliased(Address)

# of_type() form; recommended
q = (
    s.query(User)
    .join(User.addresses.of_type(a1))
    .filter(a1.email_address == "ed@foo.com")
)

# target, onclause form
q = s.query(User).join(a1, User.addresses).filter(a1.email_address == "ed@foo.com")

Псевдоним создается автоматически из-за перекрытия таблиц

Добавлено в версии 1.4.26.

Это предупреждение обычно выдается при запросе с использованием метода Select.join() или унаследованного метода Query.join() с отображениями, включающими объединенное наследование таблиц. Проблема заключается в том, что при соединении двух моделей наследования, имеющих общую базовую таблицу, правильное SQL JOIN между двумя сущностями не может быть сформировано без применения псевдонима к одной или другой стороне; SQLAlchemy применяет псевдоним к правой стороне соединения. Например, объединенное отображение наследования выглядит так:

class Employee(Base):
    __tablename__ = "employee"
    id = Column(Integer, primary_key=True)
    manager_id = Column(ForeignKey("manager.id"))
    name = Column(String(50))
    type = Column(String(50))

    reports_to = relationship("Manager", foreign_keys=manager_id)

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": type,
    }


class Manager(Employee):
    __tablename__ = "manager"
    id = Column(Integer, ForeignKey("employee.id"), primary_key=True)

    __mapper_args__ = {
        "polymorphic_identity": "manager",
        "inherit_condition": id == Employee.id,
    }

Приведенное выше отображение включает отношения между классами Employee и Manager. Поскольку оба класса используют таблицу базы данных «employee», с точки зрения SQL это self referential relationship. Если бы мы хотели сделать запрос из обеих моделей Employee и Manager с помощью объединения, на уровне SQL таблица «employee» должна быть включена в запрос дважды, что означает, что она должна быть алиасирована. Когда мы создаем такое соединение с помощью SQLAlchemy ORM, мы получаем SQL, который выглядит следующим образом:

>>> stmt = select(Employee, Manager).join(Employee.reports_to)
>>> print(stmt)
{printsql}SELECT employee.id, employee.manager_id, employee.name,
employee.type, manager_1.id AS id_1, employee_1.id AS id_2,
employee_1.manager_id AS manager_id_1, employee_1.name AS name_1,
employee_1.type AS type_1
FROM employee JOIN
(employee AS employee_1 JOIN manager AS manager_1 ON manager_1.id = employee_1.id)
ON manager_1.id = employee.manager_id

Выше, SQL выбирает FROM таблицу employee, представляющую сущность Employee в запросе. Затем он присоединяется к право-вложенному соединению employee AS employee_1 JOIN manager AS manager_1, где снова указывается таблица employee, только в качестве анонимного псевдонима employee_1. Это и есть «автоматическое создание псевдонима», на которое ссылается предупреждающее сообщение.

Когда SQLAlchemy загружает строки ORM, каждая из которых содержит объект Employee и Manager, ORM должен адаптировать строки из того, что выше является псевдонимами таблиц employee_1 и manager_1 в строки класса Manager без псевдонимов. Этот процесс внутренне сложен и не учитывает все возможности API, особенно при попытке использовать функции нетерпеливой загрузки, такие как contains_eager(), с более глубоко вложенными запросами, чем показано здесь. Поскольку этот шаблон ненадежен для более сложных сценариев и включает неявное принятие решений, которые трудно предугадать и выполнить, выдается предупреждение, и этот шаблон можно считать устаревшей функцией. Лучший способ написать этот запрос - использовать те же шаблоны, которые применяются к любым другим самореферентным отношениям, то есть явно использовать конструкцию aliased(). Для объединенного наследования и других сопоставлений, ориентированных на объединение, обычно желательно добавить использование параметра aliased.flat, который позволит объединить две или более таблиц путем применения псевдонима к отдельным таблицам внутри объединения, а не встраивать объединение в новый подзапрос:

>>> from sqlalchemy.orm import aliased
>>> manager_alias = aliased(Manager, flat=True)
>>> stmt = select(Employee, manager_alias).join(Employee.reports_to.of_type(manager_alias))
>>> print(stmt)
{printsql}SELECT employee.id, employee.manager_id, employee.name,
employee.type, manager_1.id AS id_1, employee_1.id AS id_2,
employee_1.manager_id AS manager_id_1, employee_1.name AS name_1,
employee_1.type AS type_1
FROM employee JOIN
(employee AS employee_1 JOIN manager AS manager_1 ON manager_1.id = employee_1.id)
ON manager_1.id = employee.manager_id

Если мы захотим использовать contains_eager() для заполнения атрибута reports_to, мы обратимся к псевдониму:

>>> stmt = (
...     select(Employee)
...     .join(Employee.reports_to.of_type(manager_alias))
...     .options(contains_eager(Employee.reports_to.of_type(manager_alias)))
... )

Без использования явного объекта aliased() в некоторых более вложенных случаях опция contains_eager() не имеет достаточного контекста, чтобы знать, откуда брать свои данные, в случае, если ORM осуществляет «автоалиасинг» в очень вложенном контексте. Поэтому лучше не полагаться на эту возможность и вместо этого сделать конструкцию SQL как можно более явной.

Объектно-реляционное отображение

Родительский экземпляр <x> не привязан к сессии; (ленивая загрузка/отложенная загрузка/обновление/и т.д.) операция не может быть выполнена

Это, вероятно, самое распространенное сообщение об ошибке при работе с ORM, и возникает оно в результате природы техники, широко используемой ORM, известной как lazy loading. Ленивая загрузка - это распространенный объектно-реляционный паттерн, при котором объект, сохраняемый ORM, поддерживает прокси к базе данных, так что при обращении к различным атрибутам объекта их значения могут быть получены из базы данных лениво. Преимуществом такого подхода является то, что объекты могут быть извлечены из базы данных без необходимости загрузки всех их атрибутов или связанных с ними данных сразу, а вместо этого только те данные, которые запрашиваются, могут быть доставлены в это время. Основной недостаток, по сути, является зеркальным отражением преимущества: если загружается множество объектов, для которых, как известно, во всех случаях требуется определенный набор данных, то загружать эти дополнительные данные по частям будет расточительно.

Еще одним недостатком ленивой загрузки, помимо обычных проблем эффективности, является то, что для продолжения ленивой загрузки объект должен оставаться связанным с сессией, чтобы можно было получить его состояние. Это сообщение об ошибке означает, что объект отсоединился от своего Session и его просят лениво загрузить данные из базы данных.

Наиболее распространенной причиной того, что объекты отделяются от своего Session, является закрытие сессии, обычно с помощью метода Session.close(). В дальнейшем к объектам будет осуществляться дальнейший доступ, очень часто в веб-приложениях, где они передаются серверному шаблонизатору и запрашиваются дополнительные атрибуты, которые он не может загрузить.

Устранение этой ошибки осуществляется с помощью следующих приемов:

  • Постарайтесь не иметь отсоединенных объектов; не закрывайте сессию преждевременно - Часто приложения закрывают транзакцию перед передачей связанных с ней объектов другой системе, которая затем выходит из строя из-за этой ошибки. Иногда транзакцию не нужно закрывать так скоро; пример - веб-приложение закрывает транзакцию до того, как представление будет отображено. Это часто делается во имя «корректности», но может рассматриваться как неправильное применение «инкапсуляции», поскольку этот термин относится к организации кода, а не к фактическим действиям. Шаблон, использующий объект ORM, использует proxy pattern, который сохраняет логику базы данных инкапсулированной от вызывающей стороны. Если Session можно держать открытым до тех пор, пока не закончится жизнь объектов, это лучший подход.

  • В противном случае загружайте все необходимое заранее - Очень часто невозможно держать транзакцию открытой, особенно в более сложных приложениях, которым необходимо передавать объекты другим системам, которые не могут работать в том же контексте, даже если они находятся в том же процессе. В этом случае приложение должно подготовиться к работе с объектами detached и попытаться использовать eager loading для обеспечения того, чтобы объекты имели все необходимое заранее.

  • А главное, установите expire_on_commit в False - При использовании отсоединенных объектов наиболее частой причиной того, что объекты нуждаются в повторной загрузке данных, является истечение срока их действия после последнего вызова Session.commit(). Это истечение не должно использоваться при работе с отсоединенными объектами; поэтому параметр Session.expire_on_commit должен быть установлен в False. Предотвращая истечение срока действия объектов вне транзакции, данные, которые были загружены, останутся в наличии и не будут подвергаться дополнительной ленивой загрузке при обращении к этим данным.

    Обратите также внимание, что метод Session.rollback() безоговорочно уничтожает все содержимое в Session и его также следует избегать в сценариях, не связанных с ошибками.

    См.также

    Техники загрузки отношений - подробная документация по нетерпеливой загрузке и другим техникам загрузки, ориентированным на отношения

    Совершение - фон для фиксации сессии

    Обновление / истечение срока действия - информация об истечении срока действия атрибута

Транзакция этого сеанса была откатана из-за предыдущего исключения во время flush

Процесс flush в Session, описанный в Промывка, откатит транзакцию базы данных, если возникнет ошибка, для поддержания внутренней согласованности. Однако, как только это происходит, транзакция сессии становится «неактивной» и должна быть явно откачена вызывающим приложением, точно так же, как если бы не произошел сбой, ее нужно было бы явно зафиксировать.

Это распространенная ошибка при использовании ORM и обычно относится к приложению, которое еще не имеет правильного «обрамления» вокруг своих операций Session. Более подробная информация описана в FAQ по адресу «Транзакция этого сеанса была откачена из-за предыдущего исключения во время промывки.» (или аналогично).

Для отношения <relationship> каскад delete-orphan обычно настраивается только на стороне «один» отношения один-ко-многим, но не на стороне «многие» отношения многие-к-одному или многие-ко-многим.

Эта ошибка возникает, когда параметр «delete-orphan» cascade установлен для отношения «многие-к-одному» или «многие-ко-многим», например:

class A(Base):
    __tablename__ = "a"

    id = Column(Integer, primary_key=True)

    bs = relationship("B", back_populates="a")


class B(Base):
    __tablename__ = "b"
    id = Column(Integer, primary_key=True)
    a_id = Column(ForeignKey("a.id"))

    # this will emit the error message when the mapper
    # configuration step occurs
    a = relationship("A", back_populates="bs", cascade="all, delete-orphan")


configure_mappers()

Выше, параметр «delete-orphan» для B.a указывает на намерение, что когда каждый объект B, ссылающийся на определенный A, удаляется, то A также должен быть удален. То есть, это выражает, что «сирота», который удаляется, будет объектом A, и он становится «сиротой», когда каждый B, который ссылается на него, удаляется.

Каскадная модель «delete-orphan» не поддерживает эту функциональность. Рассмотрение «сирот» производится только с точки зрения удаления одного объекта, который затем будет ссылаться на ноль или более объектов, которые теперь «осиротеют» в результате этого единственного удаления, что приведет к тому, что эти объекты также будут удалены. Другими словами, он предназначен только для отслеживания создания «сирот» на основе удаления одного и только одного «родительского» объекта на каждого сироту, что является естественным случаем в отношениях «один ко многим», когда удаление объекта на стороне «один» приводит к последующему удалению связанных с ним объектов на стороне «многие».

Приведенное выше отображение для поддержки этой функциональности вместо этого поместило бы параметр каскада на сторону «один-ко-многим», что выглядит следующим образом:

class A(Base):
    __tablename__ = "a"

    id = Column(Integer, primary_key=True)

    bs = relationship("B", back_populates="a", cascade="all, delete-orphan")


class B(Base):
    __tablename__ = "b"
    id = Column(Integer, primary_key=True)
    a_id = Column(ForeignKey("a.id"))

    a = relationship("A", back_populates="bs")

Если выражено намерение, что при удалении объекта A все объекты B, на которые он ссылается, также удаляются.

Далее в сообщении об ошибке предлагается использовать флаг relationship.single_parent. Этот флаг может быть использован для обеспечения того, что отношение, которое может иметь много объектов, ссылающихся на определенный объект, на самом деле будет иметь только один объект, ссылающийся на него в одно время. Он используется для унаследованных или других менее идеальных схем баз данных, где отношения внешних ключей предполагают наличие «многих» коллекций, однако на практике только один объект будет фактически ссылаться на данный целевой объект в данный момент времени. Этот нестандартный сценарий можно продемонстрировать в терминах приведенного выше примера следующим образом:

class A(Base):
    __tablename__ = "a"

    id = Column(Integer, primary_key=True)

    bs = relationship("B", back_populates="a")


class B(Base):
    __tablename__ = "b"
    id = Column(Integer, primary_key=True)
    a_id = Column(ForeignKey("a.id"))

    a = relationship(
        "A",
        back_populates="bs",
        single_parent=True,
        cascade="all, delete-orphan",
    )

Приведенная выше конфигурация установит валидатор, который будет следить за тем, чтобы только один B мог быть связан с A одновременно в рамках отношения B.a:

>>> b1 = B()
>>> b2 = B()
>>> a1 = A()
>>> b1.a = a1
>>> b2.a = a1
sqlalchemy.exc.InvalidRequestError: Instance <A at 0x7eff44359350> is
already associated with an instance of <class '__main__.B'> via its
B.a attribute, and is only allowed a single parent.

Обратите внимание, что этот валидатор имеет ограниченную область применения и не предотвратит создание нескольких «родителей» в другом направлении. Например, он не обнаружит одинаковые установки в терминах A.bs:

>>> a1.bs = [b1, b2]
>>> session.add_all([a1, b1, b2])
>>> session.commit()
{execsql}
INSERT INTO a DEFAULT VALUES
()
INSERT INTO b (a_id) VALUES (?)
(1,)
INSERT INTO b (a_id) VALUES (?)
(1,)

Однако в дальнейшем все пойдет не так, как ожидалось, поскольку каскад «delete-orphan» будет продолжать работать в терминах одного ведущего объекта, то есть если мы удалим любой из B объектов, то A будет удален. Другой B останется, и ORM обычно достаточно умна, чтобы установить атрибут внешнего ключа в NULL, но это обычно не то, что нужно:

>>> session.delete(b1)
>>> session.commit()
{execsql}
UPDATE b SET a_id=? WHERE b.id = ?
(None, 2)
DELETE FROM b WHERE b.id = ?
(1,)
DELETE FROM a WHERE a.id = ?
(1,)
COMMIT

Для всех вышеприведенных примеров аналогичная логика применима к вычислению отношений «многие-ко-многим»; если отношения «многие-ко-многим» устанавливают single_parent=True на одной стороне, эта сторона может использовать каскад «delete-orphan», однако это очень маловероятно, чтобы кто-то действительно хотел этого, так как смысл отношений «многие-ко-многим» заключается в том, что может быть много объектов, ссылающихся на объект в любом направлении.

В целом, каскад «удаление-сирота» обычно применяется на «одной» стороне отношения «один-ко-многим» таким образом, чтобы удалять объекты на «многих» сторонах, а не наоборот.

Изменено в версии 1.3.18: Текст сообщения об ошибке «delete-orphan» при использовании в отношениях «многие-к-одному» или «многие-ко-многим» был обновлен для большей наглядности.

Экземпляр <instance> уже связан с экземпляром <instance> через его атрибут <attribute>, и ему разрешено иметь только одного родителя.

Эта ошибка возникает, когда используется флаг relationship.single_parent, и в качестве «родителя» объекта назначается сразу несколько объектов.

Учитывая следующее отображение:

class A(Base):
    __tablename__ = "a"

    id = Column(Integer, primary_key=True)


class B(Base):
    __tablename__ = "b"
    id = Column(Integer, primary_key=True)
    a_id = Column(ForeignKey("a.id"))

    a = relationship(
        "A",
        single_parent=True,
        cascade="all, delete-orphan",
    )

Намерение указывает, что не более одного объекта B может одновременно ссылаться на определенный объект A:

>>> b1 = B()
>>> b2 = B()
>>> a1 = A()
>>> b1.a = a1
>>> b2.a = a1
sqlalchemy.exc.InvalidRequestError: Instance <A at 0x7eff44359350> is
already associated with an instance of <class '__main__.B'> via its
B.a attribute, and is only allowed a single parent.

Когда эта ошибка возникает неожиданно, обычно это происходит потому, что флаг relationship.single_parent был применен в ответ на сообщение об ошибке, описанное в Для отношения <relationship> каскад delete-orphan обычно настраивается только на стороне «один» отношения один-ко-многим, но не на стороне «многие» отношения многие-к-одному или многие-ко-многим., и проблема на самом деле заключается в неправильном понимании настройки каскада «delete-orphan». Подробности см. в этом сообщении.

отношение X скопирует столбец Q в столбец P, что противоречит отношению(ям): „Y“

Это предупреждение относится к случаю, когда два или более отношения будут записывать данные в одни и те же столбцы на флеше, но ORM не имеет никаких средств для координации этих отношений вместе. В зависимости от специфики, решение может заключаться в том, что два отношения должны быть направлены друг к другу с помощью relationship.back_populates, или что одно или несколько отношений должны быть сконфигурированы с relationship.viewonly для предотвращения конфликтующих записей, или иногда, что конфигурация полностью преднамеренна и должна быть сконфигурирована relationship.overlaps, чтобы заглушить каждое предупреждение.

Для типичного примера, в котором отсутствует relationship.back_populates, дано следующее отображение:

class Parent(Base):
    __tablename__ = "parent"
    id = Column(Integer, primary_key=True)
    children = relationship("Child")


class Child(Base):
    __tablename__ = "child"
    id = Column(Integer, primary_key=True)
    parent_id = Column(ForeignKey("parent.id"))
    parent = relationship("Parent")

Приведенное выше отображение будет генерировать предупреждения:

SAWarning: relationship 'Child.parent' will copy column parent.id to column child.parent_id,
which conflicts with relationship(s): 'Parent.children' (copies parent.id to child.parent_id).

Отношения Child.parent и Parent.children находятся в конфликте. Решением является применение relationship.back_populates:

class Parent(Base):
    __tablename__ = "parent"
    id = Column(Integer, primary_key=True)
    children = relationship("Child", back_populates="parent")


class Child(Base):
    __tablename__ = "child"
    id = Column(Integer, primary_key=True)
    parent_id = Column(ForeignKey("parent.id"))
    parent = relationship("Parent", back_populates="children")

Для более настраиваемых отношений, где ситуация «перекрытия» может быть преднамеренной и не может быть разрешена, параметр relationship.overlaps может указывать имена отношений, для которых предупреждение не должно действовать. Обычно это происходит для двух или более отношений к одной и той же базовой таблице, которые включают пользовательские условия relationship.primaryjoin, ограничивающие связанные элементы в каждом случае:

class Parent(Base):
    __tablename__ = "parent"
    id = Column(Integer, primary_key=True)
    c1 = relationship(
        "Child",
        primaryjoin="and_(Parent.id == Child.parent_id, Child.flag == 0)",
        backref="parent",
        overlaps="c2, parent",
    )
    c2 = relationship(
        "Child",
        primaryjoin="and_(Parent.id == Child.parent_id, Child.flag == 1)",
        overlaps="c1, parent",
    )


class Child(Base):
    __tablename__ = "child"
    id = Column(Integer, primary_key=True)
    parent_id = Column(ForeignKey("parent.id"))

    flag = Column(Integer)

Выше, ORM будет знать, что перекрытие между Parent.c1, Parent.c2 и Child.parent является намеренным.

Объект не может быть преобразован в «постоянное» состояние, так как эта карта идентификации больше не действительна.

Добавлено в версии 1.4.26.

Это сообщение было добавлено, чтобы учесть случай, когда объект Result, который мог бы выдать объекты ORM, итерируется после того, как исходный Session был закрыт или иным образом был вызван его метод Session.expunge_all(). Когда Session изгоняет все объекты сразу, внутренний identity map, используемый этим Session, заменяется новым, а исходный отбрасывается. Непоглощенный и небуферизованный объект Result будет внутренне поддерживать ссылку на эту удаленную карту идентичности. Поэтому, когда Result будет потреблен, объекты, которые будут получены, не смогут быть связаны с этим Session. Такая схема предусмотрена, поскольку обычно не рекомендуется итерировать небуферизованный объект Result вне транзакционного контекста, в котором он был создан:

# context manager creates new Session
with Session(engine) as session_obj:
    result = sess.execute(select(User).where(User.id == 7))

# context manager is closed, so session_obj above is closed, identity
# map is replaced

# iterating the result object can't associate the object with the
# Session, raises this error.
user = result.first()

Приведенная выше ситуация обычно **не ** возникает при использовании расширения asyncio ORM, поскольку, когда AsyncSession возвращает синхронизированный стиль Result, результаты были предварительно забуферизированы во время выполнения оператора. Это сделано для того, чтобы вторичные нетерпеливые загрузчики могли вызываться без необходимости дополнительного вызова await.

Для предварительной буферизации результатов в описанной выше ситуации с помощью обычного Session так же, как это делает расширение asyncio, можно использовать опцию выполнения prebuffer_rows следующим образом:

# context manager creates new Session
with Session(engine) as session_obj:
    # result internally pre-fetches all objects
    result = sess.execute(
        select(User).where(User.id == 7), execution_options={"prebuffer_rows": True}
    )

# context manager is closed, so session_obj above is closed, identity
# map is replaced

# pre-buffered objects are returned
user = result.first()

# however they are detached from the session, which has been closed
assert inspect(user).detached
assert inspect(user).session is None

Выше, выбранные объекты ORM полностью генерируются внутри блока session_obj, ассоциируются с session_obj и буферизируются внутри объекта Result для итерации. За пределами блока session_obj закрывается и удаляет эти объекты ORM. Итерация объекта Result приведет к появлению этих ORM-объектов, однако, поскольку их исходный Session был исключен, они будут доставлены в состоянии detached.

Примечание

Приведенная выше ссылка на «предварительно буферизованный» и «небуферизованный» объект Result относится к процессу, посредством которого ORM преобразует входящие необработанные строки базы данных из DBAPI в объекты ORM. Это не означает, является ли сам объект cursor, который представляет ожидающие результаты от DBAPI, буферизованным или небуферизованным, поскольку это, по сути, нижний уровень буферизации. О буферизации самих результатов cursor см. раздел Использование курсоров на стороне сервера (они же потоковые результаты).

Аннотация типа не может быть интерпретирована для формы аннотированной декларативной таблицы

SQLAlchemy 2.0 представляет новую декларативную систему Annotated Declarative Table, которая извлекает информацию об атрибутах, сопоставленных с ORM, из аннотаций PEP 484 в определениях классов во время выполнения. Требованием этой формы является то, что все ORM аннотации должны использовать общий контейнер Mapped, чтобы быть правильно аннотированными. Унаследованные отображения SQLAlchemy, которые включают явные аннотации типизации PEP 484, например, те, которые используют legacy Mypy extension для поддержки типизации, могут включать директивы, подобные директивам для relationship(), которые не включают этот generic.

Для решения проблемы классы могут быть помечены атрибутом __allow_unmapped__ boolean до тех пор, пока они не будут полностью переведены на синтаксис 2.0. Пример см. в примечаниях по миграции в Миграция на 2.0 Шаг шестой - добавление __allow_unmapped__ в явно типизированные модели ORM.

При преобразовании <cls> в класс данных, атрибут(ы) происходят из суперкласса <cls>, который не является классом данных.

Это предупреждение возникает при использовании функции SQLAlchemy ORM Mapped Dataclasses, описанной в Декларативное отображение классов данных, в сочетании с любым классом mixin или абстрактной базой, которая сама не объявлена как класс данных, как в примере ниже:

from __future__ import annotations

import inspect
from typing import Optional
from uuid import uuid4

from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import MappedAsDataclass


class Mixin:
    create_user: Mapped[int] = mapped_column()
    update_user: Mapped[Optional[int]] = mapped_column(default=None, init=False)


class Base(DeclarativeBase, MappedAsDataclass):
    pass


class User(Base, Mixin):
    __tablename__ = "sys_user"

    uid: Mapped[str] = mapped_column(
        String(50), init=False, default_factory=uuid4, primary_key=True
    )
    username: Mapped[str] = mapped_column()
    email: Mapped[str] = mapped_column()

Выше, поскольку Mixin сам не расширяется из MappedAsDataclass, выдается следующее предупреждение:

SADeprecationWarning: When transforming <class '__main__.User'> to a
dataclass, attribute(s) "create_user", "update_user" originates from
superclass <class
'__main__.Mixin'>, which is not a dataclass. This usage is deprecated and
will raise an error in SQLAlchemy 2.1. When declaring SQLAlchemy
Declarative Dataclasses, ensure that all mixin classes and other
superclasses which include attributes are also a subclass of
MappedAsDataclass.

Исправление заключается в том, чтобы добавить MappedAsDataclass в сигнатуру Mixin также:

class Mixin(MappedAsDataclass):
    create_user: Mapped[int] = mapped_column()
    update_user: Mapped[Optional[int]] = mapped_column(default=None, init=False)

Спецификация Python PEP 681 не учитывает атрибуты, объявленные в суперклассах классов данных, которые сами не являются классами данных; согласно поведению классов данных Python, такие поля игнорируются, как в следующем примере:

from dataclasses import dataclass
from dataclasses import field
import inspect
from typing import Optional
from uuid import uuid4


class Mixin:
    create_user: int
    update_user: Optional[int] = field(default=None)


@dataclass
class User(Mixin):
    uid: str = field(init=False, default_factory=lambda: str(uuid4()))
    username: str
    password: str
    email: str

Выше, класс User не будет включать create_user в свой конструктор и не будет пытаться интерпретировать update_user как атрибут класса данных. Это происходит потому, что Mixin не является классом данных.

Функция классов данных SQLAlchemy в серии 2.0 не поддерживает это поведение корректно; вместо этого атрибуты не относящихся к классу данных миксинов и суперклассов рассматриваются как часть окончательной конфигурации класса данных. Однако программы проверки типов, такие как Pyright и Mypy, не будут рассматривать эти поля как часть конструктора класса данных, так как они должны игнорироваться в соответствии с PEP 681. Поскольку их наличие неоднозначно, SQLAlchemy 2.1 будет требовать, чтобы классы-миксины, которые имеют атрибуты, сопоставленные SQLAlchemy, в иерархии классов данных, сами были классами данных.

Ошибка Python dataclasses, возникшая при создании класса данных для <classname>

При использовании класса MappedAsDataclass mixin или декоратора registry.mapped_as_dataclass() SQLAlchemy использует модуль Python dataclasses, входящий в стандартную библиотеку Python, чтобы применить поведение класса данных к целевому классу. Этот API имеет свои сценарии ошибок, большинство из которых включает построение метода __init__() на классе, определенном пользователем; порядок атрибутов, объявленных в классе, а также on superclasses, определяет, как будет построен метод __init__(), и есть определенные правила в том, как атрибуты организованы, а также как они должны использовать параметры, такие как init=False, kw_only=True и т.д. SQLAlchemy не контролирует и не реализует эти правила. Поэтому при ошибках такого рода обратитесь к документации Python dataclasses, обращая особое внимание на правила, применяемые к inheritance.

См.также

Декларативное отображение классов данных - Документация по классам данных SQLAlchemy

Python dataclasses - на сайте python.org

inheritance - на сайте python.org

Per-row ORM Bulk Update by Primary Key требует, чтобы записи содержали значения первичного ключа

Эта ошибка возникает при использовании функции ORM Bulk UPDATE по первичному ключу без указания значений первичного ключа в заданных записях, например:

>>> session.execute(
...     update(User).where(User.name == bindparam("u_name")),
...     [
...         {"u_name": "spongebob", "fullname": "Spongebob Squarepants"},
...         {"u_name": "patrick", "fullname": "Patrick Star"},
...     ],
... )

Выше, наличие списка словарей параметров в сочетании с использованием Session для выполнения оператора UPDATE с поддержкой ORM будет автоматически использовать ORM Bulk Update by Primary Key, который ожидает, что словари параметров будут включать значения первичного ключа, например:

>>> session.execute(
...     update(User),
...     [
...         {"id": 1, "fullname": "Spongebob Squarepants"},
...         {"id": 3, "fullname": "Patrick Star"},
...         {"id": 5, "fullname": "Eugene H. Krabs"},
...     ],
... )

Чтобы вызвать оператор UPDATE без предоставления значений первичного ключа для каждой записи, используйте Session.connection() для получения текущего Connection, а затем вызовите его с этим:

>>> session.connection().execute(
...     update(User).where(User.name == bindparam("u_name")),
...     [
...         {"u_name": "spongebob", "fullname": "Spongebob Squarepants"},
...         {"u_name": "patrick", "fullname": "Patrick Star"},
...     ],
... )

Исключения AsyncIO

AwaitRequired

Асинхронный режим SQLAlchemy требует использования асинхронного драйвера для подключения к базе данных. Эта ошибка обычно возникает при попытке использовать асинхронную версию SQLAlchemy с несовместимым DBAPI.

MissingGreenlet

Вызов async DBAPI был инициирован вне контекста спавна гринлета, обычно устанавливаемого прокси-классами SQLAlchemy AsyncIO. Обычно эта ошибка возникает, когда ввод-вывод был предпринят в неожиданном месте, с использованием шаблона вызова, который напрямую не предусматривает использование ключевого слова await. При использовании ORM это почти всегда связано с использованием lazy loading, которое не поддерживается непосредственно в asyncio без дополнительных шагов и/или альтернативных шаблонов загрузчика для успешного использования.

См.также

Предотвращение неявного ввода-вывода при использовании AsyncSession - охватывает большинство ORM-сценариев, в которых может возникнуть эта проблема, и способы ее решения, включая конкретные шаблоны для использования в сценариях ленивой загрузки.

Осмотр не доступен

Использование функции inspect() непосредственно на объекте AsyncConnection или AsyncEngine в настоящее время не поддерживается, поскольку еще не существует ожидаемой формы объекта Inspector. Вместо этого объект используется путем его получения с помощью функции inspect() таким образом, что она ссылается на базовый атрибут AsyncConnection.sync_connection объекта AsyncConnection; затем объект Inspector используется в «синхронном» стиле вызова с помощью метода AsyncConnection.run_sync() вместе с пользовательской функцией, выполняющей необходимые операции:

async def async_main():
    async with engine.connect() as conn:
        tables = await conn.run_sync(
            lambda sync_conn: inspect(sync_conn).get_table_names()
        )

См.также

Использование инспектора для проверки объектов схемы - дополнительные примеры использования inspect() с расширением asyncio.

Основные классы исключений

Классы исключений Core см. в Основные исключения.

Классы исключений ORM

Классы исключений ORM см. в Исключения ORM.

Исключения из наследия

Исключения в этом разделе не генерируются текущими версиями SQLAlchemy, однако приведены здесь, чтобы соответствовать гиперссылкам на сообщения об исключениях.

Функция <somome function> в SQLAlchemy 2.0 больше не будет <something

SQLAlchemy 2.0 представляет собой значительный сдвиг для широкого спектра ключевых моделей использования SQLAlchemy в компонентах Core и ORM. Цель релиза 2.0 - внести небольшие изменения в некоторые из наиболее фундаментальных предположений SQLAlchemy с момента ее зарождения и предоставить новую оптимизированную модель использования, которая, как ожидается, будет значительно более минималистичной и последовательной между компонентами Core и ORM, а также более функциональной.

Представленный в SQLAlchemy 2.0 - руководство по миграции, проект SQLAlchemy 2.0 включает в себя комплексную систему будущей совместимости, интегрированную в серию SQLAlchemy 1.4, благодаря которой приложения будут иметь четкий, однозначный и постепенный путь обновления, чтобы перевести приложения на полную совместимость с 2.0. Предупреждение об устаревании RemovedIn20Warning лежит в основе этой системы, чтобы предоставить рекомендации о том, какие модели поведения в существующей кодовой базе необходимо изменить. Обзор того, как включить это предупреждение, находится в Режим исключений в SQLAlchemy 2.0.

См.также

SQLAlchemy 2.0 - руководство по миграции - Обзор процесса обновления с серии 1.x, а также текущих целей и прогресса SQLAlchemy 2.0.

Режим исключений в SQLAlchemy 2.0 - конкретные указания по использованию «режима 2.0 deprecations» в SQLAlchemy 1.4.

Объект объединяется в сессию по каскаду обратных ссылок

Это сообщение относится к поведению «каскада обратных ссылок» в SQLAlchemy, удаленному в версии 2.0. Это относится к действию, когда объект добавляется в Session в результате того, что другой объект, который уже присутствует в этой сессии, ассоциируется с ним. Поскольку было показано, что такое поведение больше запутывает, чем помогает, были добавлены параметры relationship.cascade_backrefs и backref.cascade_backrefs, которые можно установить в False, чтобы отключить его, а в SQLAlchemy 2.0 поведение «каскадных обратных ссылок» было полностью удалено.

Для старых версий SQLAlchemy, чтобы установить relationship.cascade_backrefs в False на обратную ссылку, которая в настоящее время настроена с помощью строкового параметра relationship.backref, обратная ссылка должна быть сначала объявлена с помощью функции backref(), чтобы можно было передать параметр backref.cascade_backrefs.

В качестве альтернативы, все поведение «каскадных обратных ссылок» можно отключить, используя Session в режиме «будущего», передавая True для параметра Session.future.

См.также

поведение cascade_backrefs deprecated для удаления в 2.0 - предыстория изменений для SQLAlchemy 2.0.

конструкция select(), созданная в «унаследованном» режиме; аргументы ключевых слов и т.д.

Конструкция select() была обновлена в SQLAlchemy 1.4 для поддержки нового стиля вызова, который является стандартным в SQLAlchemy 2.0. Для обратной совместимости в рамках серии 1.4 конструкция принимает аргументы как в «старом», так и в «новом» стиле.

Особенностью «нового» стиля является то, что выражения столбцов и таблиц передаются только позиционно в конструкцию select(); любые другие модификаторы объекта должны передаваться с помощью последующей цепочки методов:

# this is the way to do it going forward
stmt = select(table1.c.myid).where(table1.c.myid == table2.c.otherid)

Для сравнения, select() в устаревших формах SQLAlchemy, до того как были добавлены методы типа Select.where(), имел вид:

# this is how it was documented in original SQLAlchemy versions
# many years ago
stmt = select([table1.c.myid], whereclause=table1.c.myid == table2.c.otherid)

Или даже то, что «whereclause» будет передаваться позиционно:

# this is also how it was documented in original SQLAlchemy versions
# many years ago
stmt = select([table1.c.myid], table1.c.myid == table2.c.otherid)

Уже несколько лет как дополнительные «whereclause» и другие принимаемые аргументы были удалены из большинства описательной документации, что привело к стилю вызова, который наиболее знаком как список аргументов столбцов, передаваемых в виде списка, но без дополнительных аргументов:

# this is how it's been documented since around version 1.0 or so
stmt = select([table1.c.myid]).where(table1.c.myid == table2.c.otherid)

Документ по адресу select() больше не принимает переменные аргументы конструктора, столбцы передаются позиционно описывает это изменение в терминах 2.0 Migration.

Привязка была найдена через унаследованные метаданные привязки, но поскольку для этой сессии установлено future=True, эта привязка игнорируется.

Концепция «связанных метаданных» присутствует вплоть до версии SQLAlchemy 1.4; начиная с версии SQLAlchemy 2.0 она была удалена.

Эта ошибка относится к параметру MetaData.bind на объекте MetaData, который, в свою очередь, позволяет объектам типа ORM Session связывать определенный сопоставленный класс с Engine. В SQLAlchemy 2.0, Session должен быть связан с каждым Engine непосредственно. То есть, вместо инстанцирования Session или sessionmaker без каких-либо аргументов и связывания Engine с MetaData:

engine = create_engine("sqlite://")
Session = sessionmaker()
metadata_obj = MetaData(bind=engine)
Base = declarative_base(metadata=metadata_obj)


class MyClass(Base):
    ...


session = Session()
session.add(MyClass())
session.commit()

Вместо этого Engine должен быть связан непосредственно с sessionmaker или Session. Объект MetaData больше не должен быть связан ни с каким двигателем:

engine = create_engine("sqlite://")
Session = sessionmaker(engine)
Base = declarative_base()


class MyClass(Base):
    ...


session = Session()
session.add(MyClass())
session.commit()

В SQLAlchemy 1.4 это поведение 2.0 style включено, когда флаг Session.future установлен на sessionmaker или Session.

Этот скомпилированный объект не привязан ни к движку, ни к соединению

Эта ошибка относится к концепции «связанных метаданных», которая является унаследованным шаблоном SQLAlchemy, присутствующим только в версиях 1.x. Проблема возникает, когда вызывается метод Executable.execute() непосредственно из объекта выражения Core, который не связан ни с каким Engine:

metadata_obj = MetaData()
table = Table("t", metadata_obj, Column("q", Integer))

stmt = select(table)
result = stmt.execute()  # <--- raises

Логика ожидает, что объект MetaData был связан с объектом Engine:

engine = create_engine("mysql+pymysql://user:pass@host/db")
metadata_obj = MetaData(bind=engine)

Там, где указано выше, любое утверждение, вытекающее из Table, которое в свою очередь вытекает из MetaData, будет неявно использовать данное Engine для вызова утверждения.

Обратите внимание, что концепция связанных метаданных отсутствует в SQLAlchemy 2.0. Правильный способ вызова утверждений - через метод Connection.execute() в Connection:

with engine.connect() as conn:
    result = conn.execute(stmt)

При использовании ORM аналогичная возможность доступна через Session:

result = session.execute(stmt)

Это соединение находится в неактивной транзакции. Пожалуйста, выполните полный откат(), прежде чем продолжить

Это состояние ошибки было добавлено в SQLAlchemy начиная с версии 1.4 и не относится к SQLAlchemy 2.0. Ошибка относится к состоянию, когда транзакция Connection помещается в транзакцию с помощью метода типа Connection.begin(), а затем в этой области создается еще одна «маркерная» транзакция; затем «маркерная» транзакция сворачивается с помощью Transaction.rollback() или закрывается с помощью Transaction.close(), однако внешняя транзакция все еще находится в «неактивном» состоянии и должна быть свернута.

Схема выглядит следующим образом:

engine = create_engine(...)

connection = engine.connect()
transaction1 = connection.begin()

# this is a "sub" or "marker" transaction, a logical nesting
# structure based on "real" transaction transaction1
transaction2 = connection.begin()
transaction2.rollback()

# transaction1 is still present and needs explicit rollback,
# so this will raise
connection.execute(text("select 1"))

Выше, transaction2 - это «маркерная» транзакция, которая указывает на логическую вложенность транзакций внутри внешней транзакции; в то время как внутренняя транзакция может откатить всю транзакцию через свой метод rollback(), ее метод commit() не имеет никакого эффекта, кроме закрытия области действия самой «маркерной» транзакции. Вызов transaction2.rollback() имеет эффект деактивации транзакции1, что означает ее откат на уровне базы данных, однако она все еще присутствует, чтобы обеспечить последовательную схему вложенности транзакций.

Правильным решением является обеспечение отката внешней транзакции:

transaction1.rollback()

Этот шаблон не часто используется в Core. В ORM может возникнуть аналогичная проблема, которая является результатом «логической» структуры транзакций ORM; она описана в FAQ по адресу «Транзакция этого сеанса была откачена из-за предыдущего исключения во время промывки.» (или аналогично).

Шаблон «субтранзакция» удален в SQLAlchemy 2.0, так что этот конкретный шаблон программирования больше не будет доступен, предотвращая появление этого сообщения об ошибке.

Вернуться на верх