2.0.55
Released: October 16, 2026
orm
-
[orm] [bug] Fixed issue in Annotated Declarative where an annotation naming an undefined symbol, where the annotation is not wrapped in
_orm.Mapped, would raiseTypeErrorrather than the expectedMappedAnnotationErrorwhen running under Python 3.14 withoutfrom __future__ import annotationsin effect. Under PEP 649, such an annotation is delivered as aForwardRefrather than a string, a form which was not accommodated by the check that determines whether an unresolvable annotation was intended to be mapped.References: #13602
-
[orm] [bug] [regression] Fixed regression caused by #11917 in version 2.0.36 where an ORM enabled
_sql.update()using WHERE criteria which matched more than one object present in the_orm.Sessionwould expire server-side onupdate columns, as well as columns that were SET to a SQL expression, on only one of the matched objects, leaving the remaining objects with stale values for those attributes. The columns are now expired for all matched objects.References: #13675
orm extensions
-
[bug] [orm extensions] Fixed issue in the
mutable_toplevelextension where the in-place|=operator onMutableDictand the in-place*=operator onMutableListdid not emit change events, so that the modification was not detected and was not persisted on the next flush. These operators now invokeMutable.changed()in the same way as the in-place set operators ofMutableSetand the+=operator ofMutableList.References: #13643
engine
-
[engine] [bug] [regression] Fixed regression from the 1.4 series where the DBAPI cursor used for a compiled statement that returns no rows, which includes DDL constructs such as
CreateTableandDropTableas well as the SAVEPOINT, ROLLBACK TO SAVEPOINT and RELEASE SAVEPOINT statements emitted by_engine.Connection.begin_nested(), was not closed after execution, and was instead left to be released when garbage collected. The cursor is now closed immediately, as was the case in 1.3 and as is already the case for INSERT, UPDATE, DELETE and_sql.text()statements.References: #13640
-
[engine] [bug] Fixed issue in the
_engine.Connection.execution_options.logging_tokenoption where setting the value toNoneor another false value would raiseAttributeErrorif a logging token had not already been established on that_engine.Connection, such as on a newly checked out connection, when the option was set at the_engine.Enginelevel, or when the token was reset toNonemore than once. Regression introduced in 2.0.30 as part of #11210.References: #13641
sql
-
[sql] [performance] Added instance-level caching to the "comparator factory" method of the
TypeDecoratorclass. This attribute is used in most SQL comparison operations and was generating a new class on the fly each time it was called. Special steps have been taken to allow this class to be cached while still allowing for pickling of theTypeDecoratoritself.References: #13617
-
[sql] [bug] Fixed an additional race condition related to those fixed as part of #12302, where concurrent access to the
FromClause.primary_keyorFromClause.foreign_keyscollections on a newly createdAliasorSubquerycould raise anAssertionErrorif another thread was in the midst of generating these collections.References: #12302
-
[sql] [bug] Fixed issue where literal text within a SQL statement that resembled a
pyformatbound parameter, such as%(name)s, would be rewritten as a bound parameter placeholder when compiling for a dialect using a positional paramstyle such asqmark,format,numericornumeric_dollar. This would occur for string values rendered with theliteral_bindscompiler option, as well as for literal text within_sql.text()or_sql.literal_column()constructs, leading to a SQL statement that silently compared against a different value, or that raised aKeyErrorfor numeric paramstyles. This was a regression caused by the change in the 2.0 series where statements for positional paramstyles are first compiled using named parameters, which are then converted to positional placeholders.References: #13609
-
[sql] [bug] The rendering of bound parameters within a
_sql.text()construct no longer considers the$character to be part of a bound parameter name, matching the parsing already performed by_sql.text()itself when the construct is created. Previously, a string such as:foo$barwould be rendered as a single bound parameter namedfoo$bar, for which a value could not be passed, and which would fail when executed. The string is now rendered as the bound parameterfoofollowed by the literal text$bar. Additionally,_sql.text()no longer establishes a spurious bound parameter from a name that is immediately followed by a colon, such asthifor the string:this:thatorvafor the string:val::int; these strings are not rendered as bound parameters, and_sql.text()now uses the same parsing as the compiler to determine this.References: #13609
-
[sql] [bug] Fixed issue where the
UpdateBase.return_defaults.supplemental_colsparameter ofUpdateBase.return_defaults()was not included in the cache key of anInsertorUpdateconstruct, causing a statement that used this parameter to share a cached compiled form with an otherwise identical statement that did not, or that used different supplemental columns. This would cause the supplemental RETURNING rows to be missing or incorrect depending on which form of the statement was compiled first.
schema
-
[schema] [bug] Fixed issue where dialect-specific keyword arguments established on a
ForeignKeyConstraint,ForeignKeyorCheckConstraint, such aspostgresql_not_valid, were not carried along to the copy of the constraint produced byTable.to_metadata(), so that the copiedTablewould generate DDL without these options. The fix for #5276 had established this behavior forColumn,PrimaryKeyConstraintandUniqueConstraintonly.References: #13677
asyncio
-
[asyncio] [bug] Fixed issue where cancelling a task while an asyncio context manager such as
_asyncio.AsyncConnectionor_asyncio.AsyncSessionwas closing on exit would allow the task to complete while the close continued in the background. An application that disposed of the engine at that point, as is typical at shutdown, would have the connection returned to the disposed pool afterwards, where nothing would close it. The context manager now waits for the close to complete before propagating the cancellation.Also fixed related issues in the connection pool where a cancellation received while a connection was being reset on return to the pool, or a second cancellation received while the pool was cleaning up after a first cancellation during checkout, would prevent the connection's slot from being returned to the pool, reducing the pool's capacity until the connection object was garbage collected, or permanently in the latter case.
-
[asyncio] [bug] Fixed issue where a task that was cancelled a second time while the connection pool was closing an asyncio connection invalidated by the first cancellation, such as one received while a statement was executing, would leave the closed connection in place in the pool, where it would be handed out to the next checkout and fail upon first use. The pool now discards the connection in all cases once a close has been attempted. This condition occurs unconditionally for tasks cancelled by an AnyIO cancel scope, which delivers the cancellation repeatedly until the task exits the scope.
References: #13673
-
[asyncio] [bug] Fixed regression caused by #13548 where an asyncio cancellation delivered while the pool was closing a connection stranded by an exception raised within the
_events.PoolEvents.connect()event handler would be suppressed, with the original exception raised in its place. The task would then not end as cancelled, and code that catches ordinary exceptions in order to retry would continue running after it had been cancelled. TheCancelledErroris now propagated.As part of this change, a variety of other cancellation scenarios were caught and repaired.
References: #13678
postgresql
-
[postgresql] [bug] [reflection] Fixed issue in PostgreSQL reflection where a composite primary key or unique constraint could be reflected with some of its columns missing, with those columns instead reported as
postgresql_includecolumns, resulting in an incorrect primary key on the reflectedTable; foreign key and check constraints could also be reflected more than once. The issue could occur in databases where comments are present on other objects, such as databases that were upgraded usingpg_upgrade. Credit to Kirill Kozahenko for the patch.References: #13615
-
[postgresql] [bug] Fixed regression where copying a
_postgresql.DOMAINdatatype, as occurs when usingTypeEngine.copy(),Table.to_metadata(), or when aColumnreferring to a_postgresql.DOMAINis placed on a declarative mixin, would drop the_postgresql.DOMAIN.collation,_postgresql.DOMAIN.default,_postgresql.DOMAIN.constraint_name,_postgresql.DOMAIN.not_nulland_postgresql.DOMAIN.checkparameters, causing theCREATE DOMAINstatement emitted for the copied type to omit these elements. The regression was introduced in version 2.0.42.Additionally, copies of
_postgresql.ENUMnow retain the_types.Enum.sort_key_function,_types.Enum.create_constraintand_types.Enum.lengthparameters, and copies of_types.Enumnow retain the_types.Enum.sort_key_functionparameter, all of which were previously reset to their default values.References: #13644
-
[postgresql] [bug] Fixed issue where reflection of foreign keys on PostgreSQL 18 and above which included the
PERIODkeyword would fail. A constraint using this keyword is currently reflected as a plain foreign key and thePERIODdesignation is discarded. Inclusion of thePERIODdesignation itself in the inspector dictionary or as part of aForeignKeyConstraintobject is not yet supported but may be in a future release.References: #13664
mssql
-
[mssql] [bug] Fixed issue in the pyodbc dialect for SQL Server where timezone-aware datetime values sent to a
_mssql.DATETIMEOFFSETorDateTimecolumn withDateTime.timezoneset would be rendered without zero-padding the year on Python versions prior to 3.14 on some platforms such as Linux, causing years before 1000 to fail, or the year 1 to be silently stored as a different date. The value is now rendered usingdatetime.isoformat().References: #13610
-
[mssql] [bug] Fixed issue where the Python true division operator
/would truncate the fractional part of the divisor on SQL Server when the divisor's type was not numeric, such as when using a SQL function likeNULLIF()that has no known return type. The divisor was rendered asCAST(... AS NUMERIC), which on SQL Server implies a scale of zero; the divisor is now rendered as(... + 0.0), the same approach used by the SQLite dialect, which promotes it to a decimal value without truncation. Pull request courtesy James Guthrie.References: #13631