Repository navigation
Releases: sqlalchemy/sqlalchemy
Release list
2.1.6
2.1.6
Released: October 21, 2026
orm
-
[orm] [bug] Fixed issue where a
Sessionflush that replaced an expired
object in the identity map with a newly flushed object of the same
primary key would emit a SELECT to refresh the expired object, solely in
order to determine whether to emit an "Identity map already had an
identity" warning. This could occur when a row loaded within a
transaction is rolled back and its primary key value is subsequently
reused, and could lead to anAttributeErrorif the expired object
were garbage collected during that load. The check now emits no SQL, and
an expired object that is replaced in this way no longer causes the
warning to be emitted.This change is also backported to: 2.0.56
References: #13711
-
[orm] [bug] [regression] Fixed regression caused by #13070 where using more than one
orm.with_loader_criteria()option for the same entity, where the
criteria made use of correlated subqueries referring to that entity, would
fail with aRecursionErrorwhen compiling.References: #13701
-
[orm] [bug] [regression] Fixed regression caused by the new implementation of
.params()for
statements introduced in #7066, where parameter values passed to
the.params()method of an ORM statement created using
Select.from_statement()would not be applied when the statement
was compiled without the compiled statement cache, such as when the cache
was disabled or when the statement was stringified directly, leading to
an error that a value is required for the bound parameter.References: #13709
orm extensions
-
[bug] [orm extensions] Fixed an issue in the
instrumentation_toplevelextension where
unrelated classes inheriting fromtyping.Genericcould incorrectly
raise aTypeErrorwhen using different instrumentation implementations.
Pull request courtesy TomasCaruso10.This change is also backported to: 2.0.56
References: #13680
engine
-
[engine] [usecase] Added
make_url.keep_blank_valuesand
URL.update_query_string.keep_blank_valuesparameters, which
when set to True retain query string parameters with blank values such as
key=in theURL.querydictionary as empty strings. This allows
aURLwith empty-string query values to round trip through
URL.render_as_string()andmake_url(). The default behavior
of omitting blank values is unchanged, as dialects may not accept an empty
string for query parameters that are coerced to other types.References: #13697
sql
-
[sql] [bug] Fixed issues in cache key generation involving the DML constructs
Insert,UpdateandDelete. The
Insert.from_select.include_defaults,
UpdateBase.return_defaults()and
UpdateBase.return_defaults.supplemental_cols
parameters/attributes are now considered as part of the cache key.This change is also backported to: 2.0.56
-
[sql] [bug] Fixed issue where the individual bound parameters generated for an
"expanding" parameter, such as that of an IN expression, at statement
execution time could take on the same name as another bound parameter in
the same statement, causing the value of the other parameter to be
silently replaced with that of an element of the IN list. Names are now
disambiguated from one another in this scenario.This change is also backported to: 2.0.56
References: #13695
asyncio
-
[asyncio] [bug] Fixed issue where calling
_asyncio.AsyncResult.unique()or
_asyncio.AsyncResult.columns()on an_asyncio.AsyncResult
would have no effect on a subsequent call to
_asyncio.AsyncResult.scalars()or
_asyncio.AsyncResult.mappings(), as the resulting
_asyncio.AsyncScalarResultor
_asyncio.AsyncMappingResultwas built from the underlying
synchronous result, which did not include those modifications. Unique
filtering and column selection are now carried over in the same way as for
_engine.Result.scalars()and_engine.Result.mappings(). Among
other things, this allows_asyncio.AsyncSession.stream()to be used
with a joined eager load against a collection, followed by
.unique().scalars(), which would previously raise an error indicating
that_asyncio.AsyncResult.unique()must be invoked.This change is also backported to: 2.0.56
References: #13688
-
[asyncio] [bug] Fixed issue where the
_asyncio.AsyncResult.freeze()method would
fail to fetch rows from the result, producing a
_engine.FrozenResultthat would raiseTypeErrorwhen invoked
for a Core result, or raisingAttributeErrorimmediately for an ORM
result that returns a single entity per row. The method now fully
fetches all rows from the async cursor, honoring
_asyncio.AsyncResult.unique()and
_asyncio.AsyncResult.columns()in the same way as the synchronous
_engine.Result.freeze()method.This change is also backported to: 2.0.56
References: #13689
-
[asyncio] [bug] Fixed regression caused by #12710 where a cancellation received
by_asyncio.AsyncConnection.close()was not handled properly,
leaving the_asyncio.AsyncConnectionin an inconsistent state
that could interfere with connections subsequently checked out from the
pool. The connection is now fully closed in this case.This change is also backported to: 2.0.56
References: #13699
mysql
-
[mysql] [bug] Fixed issue where backslashes in the values of a
mysql.ENUMor
mysql.SETtype were not escaped when rendered in DDL, leading to
incorrect values or a malformed statement under MySQL's default
sql_mode. Backslashes are now escaped in the same way as for other
string literals, unlessNO_BACKSLASH_ESCAPESis in effect. Pull
request courtesy dxbjavid.This change is also backported to: 2.0.56
References: #13466
2.1.5
2.1.5
Released: October 16, 2026
orm
-
[orm] [feature] Added the
_orm.Unmappedannotation and the
_orm.unmapped_column()construct, providing a declarative-compatible
approach to having "excluded" columns in a Declarative model, that is,
columns that are part of the_schema.Tablebut are not mapped,
replacing the use of_orm.Mapper.exclude_propertiesfor this
purpose when using a Declarative mapping.References: #13650
-
[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.This change is also backported to: 2.0.55
References: #13675
-
[orm] [deprecated] Using the
_orm.Mapper.exclude_propertiesor
_orm.Mapper.include_propertiesparameters on a Declarative
mapping to exclude attributes that are declared on the class, such as with
_orm.mapped_column(), is deprecated and now emits a deprecation
warning. This use was never supported, and depending on the arguments
passed to_orm.mapped_column(), the attribute could remain mapped
regardless; in particular, as of 2.1 this includes ORM mapped dataclasses
where_orm.mapped_column.defaultis present. The new
_orm.unmapped_column()construct should be used instead. Excluding
columns that are present only in the_schema.Table, such as those
from__table__,__table_args__or table reflection, is not
affected.References: #13650
schema
-
[schema] [bug] Fixed issue where dialect-specific keyword arguments established on a
ForeignKeyConstraint,ForeignKeyor
CheckConstraint, such aspostgresql_not_valid, were not
carried along to the copy of the constraint produced by
Table.to_metadata(), so that the copiedTablewould
generate DDL without these options. The fix for #5276 had
established this behavior forColumn,
PrimaryKeyConstraintandUniqueConstraintonly.This change is also backported to: 2.0.55
References: #13677
asyncio
-
[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.This change is also backported to: 2.0.55
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.This change is also backported to: 2.0.55
References: #13678
2.0.55
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 expected
MappedAnnotationErrorwhen running under Python 3.14 without
from __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 asCreateTableandDropTableas 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 raise
AttributeErrorif 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 theTypeDecorator
itself.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_keys
collections on a newly createdAliasorSubquery
could 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,numericor
numeric_dollar. This would occur for string values rendered with the
literal_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:thator
vafor 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 of
UpdateBase.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,ForeignKeyor
CheckConstraint, such aspostgresql_not_valid, were not
carried along to the copy of the constraint produced by
Table.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.AsyncSession
was 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 ref...
2.1.4
2.1.4
Released: October 7, 2026
orm
-
[orm] [bug] [regression] Fixed regression caused by #13333 where assigning a
MetaDataobject to themetadataattribute of a mapped
Declarative class, in order to place that class'sTablein a
MetaDatacollection other than that of the
_orm.registry, would emit a warning that the attribute name
metadatashould be left reserved. The warning is now emitted only
when themetadataattribute is assigned to an object other than a
MetaData.References: #13659
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 asCreateTableandDropTableas 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.This change is also backported to: 2.0.55
References: #13640
-
[engine] [bug] Fixed issue in the
_engine.Connection.execution_options.logging_tokenoption
where setting the value toNoneor another false value would raise
AttributeErrorif 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.This change is also backported to: 2.0.55
References: #13641
-
[engine] [bug] [regression] Fixed regression where iterating a
Resultdirectly would create
reference cycles that prevented theResultalong with the DBAPI
cursor from being freed immediately, instead relying upon cyclic GC to free
it; also fixed a similar issue when using scalar-returning methods such as
Result.scalars(). The issues were introduced as part of a 2.1
refactoring ofResultinto Cython code.References: #13639
-
[engine] [bug] Fixed issue where an exception raised by the rollback performed within
_engine.Connection.close(), which was not otherwise classified as a
disconnect, would leave the_engine.Connectionopen and its
pooled DBAPI connection checked out, emitting a warning when later
garbage collected; for an asyncio driver, the connection could not be
closed at that point and was leaked. The DBAPI connection is now
returned to the pool, which resets it as usual, invalidating it if that
reset fails as well, and the original exception is raised.References: #13657
sql
-
[sql] [bug] Fixed issue where the
UpdateBase.return_defaults.supplemental_colsparameter of
UpdateBase.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.This change is also backported to: 2.0.55
-
[sql] [bug] [regression] Fixed regression where stringifying a statement that includes the
_functions.aggregate_stringsfunction, such as viastr(),
would raiseTypeError. Additionally, compiling the function with the
default dialect or with a third party dialect that doesn't provide its own
rendering for this function now raisesCompileErrorrather than
TypeError.References: #13642
postgresql
-
[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 the
CREATE 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.This change is also backported to: 2.0.55
References: #13644
-
[postgresql] [bug] [regression] Fixed regression in the asyncpg dialect where a failure to start a
transaction, such as when an asyncpg connection is used from a different
event loop than the one it was created on, would leave the failed asyncpg
transaction in place, so that the subsequent rollback raised an
InterfaceErrorthat masked the original error.References: #13657
mssql
-
[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.This change is also backported to: 2.0.55
References: #13631
misc
-
[bug] [ext] 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.This change is also backported to: 2.0.55
References: #13643
2.1.3
2.1.3
Released: October 2, 2026
orm
-
[orm] [bug] [regression] Fixed regression where columns delivered to a mapped class from an
unmapped_orm.MappedAsDataclassmixin, or from a class decorated
with_orm.unmapped_dataclass(), would not be placed in the
_schema.Tablein the order in which they were declared on the
mixin; columns that had no dataclass default, such as a primary key column
with no_orm.mapped_column.default, or which made use of
_orm.mapped_column.default_factory, would be moved after the
remaining columns of the mixin.References: #13634
sql
-
[sql] [bug] [regression] Fixed regression caused by the new implementation of
ExecutableStatement.params()where parameter values established
using this method would not be rendered when compiling the statement with
theliteral_bindscompiler option, instead rendering NULL with a
warning.References: #13635
2.1.2
2.1.2
Released: October 2, 2026
orm
-
[orm] [bug] [regression] Fixed regression caused by #5987 where the
selectinload()
loader strategy would ignore additional criteria present in the
_orm.relationship.primaryjoinof a many-to-many relationship,
such as a comparison against a column on the association table or on the
parent table, loading related rows that should have been excluded. The
omit_joinoptimization for many-to-many relationships is now only used
when the primaryjoin consists solely of comparisons between the parent's
primary key columns and the association table.References: #13626
engine
-
[engine] [bug] [regression] Fixed regression where result rows delivered by the DBAPI as a subclass of
tuplewould fail to be processed when the Cython extensions were in
use, raisingTypeError: Expected tuple. Such rows are now converted to
a plaintuple, as was the case in 2.0. Rows delivered as a plain
tuple, or as a sequence that is not atupleat all such as
asyncpg'sRecord, were not affected. Drivers such as the Databricks
SQL connector return rows that aretuplesubclasses.References: #13619
sql
-
[sql] [bug] The plain string portions of a
_sql.tstring()construct are no
longer scanned for bound parameter names in the:nameformat, and no
longer interpret the\:escape sequence; the text is now rendered
exactly as given. Previously, each string portion was parsed in the same
way as_sql.text(), so that a colon within a quoted SQL string, such
as'time is :now', would be rendered as a bound parameter, failing at
execution time.This change is a breaking change in the behavior of the newly introduced
_sql.tstring()feature, as the former use oftext()
inadvertently provided a behavior that wasn't intended. In order to embed
an explicit parameter in the SQL generated by_sql.tstring(),
_sql.bindparam()may be used directly, e.g.
tstring(t"SELECT {bindparam('p')}").References: #13616
schema
-
[schema] [bug] [regression] Fixed issue where the internal copies of
Enum,
_postgresql.DOMAINand otherSchemaTypetypes produced
when the type is adapted to a dialect-specific implementation would
register themselves with theMetaDatacollection that is queried
byMetaData.get_schema_objects(), in addition to the original type.
As a new dialect-level copy is produced for each newDialect
instance, such as each timestr()is called on a statement referring
to a column usingEnum, the collection would grow without bound
and lead to excessive memory use. Only the type constructed by the user
is now registered.References: #13625
2.1.1
2.1.1
Released: September 25, 2026
platform
-
[platform] [bug] Removed the legacy underscore-separated extra names such as
mssql_pymssqlandpostgresql_psycopgfrompyproject.toml.
They normalize to the same names as the existing dash-separated extras,
which is disallowed by PEP 685, and caused the 2.1.0 source
distribution to fail to build with installers that enforce this rule,
such as uv. The underscore spellings continue to work when installing,
as installers normalize extra names before matching them.References: #13604
2.1.0
2.1.0
Released: September 24, 2026
orm
-
[orm] [feature] Added
_orm.composite.column_templateparameter to
_orm.composite(). When the composite class is a dataclass, this
parameter accepts a string template such as"person_%s", containing
exactly one%splaceholder, that's used to generate column names for
dataclass fields that don't otherwise have an explicit name, rather than
using the bare field name. This removes the need to hand-write a
_orm.mapped_column()for each field when the same composite dataclass
is mapped multiple times on the same class with different column-name
prefixes. Pull request courtesy Leonardo Rosa.References: #12575
-
[orm] [bug] Fixed issue where pickling an ORM object that had an instance level lazy
loader established, such as when the_orm.raiseload()option is used,
would emit a spurious warning regarding the loader containing additional
criteria, if the object had itself been unpickled from a previous
serialization. This would occur for objects that cross more than one
serialization boundary, such as when using multiprocessing.This change is also backported to: 2.0.53
References: #13574
-
[orm] [bug] Fixed issue where calling
_orm.aliased()against an existing
_orm.aliased()construct, without passing an explicit selectable,
would disregard the selectable of the existing construct and produce an
alias of the mapped table instead, if that selectable were anything other
than a table or a plain subquery, leading to incorrect results and/or
non-working queries.This includes
_orm.aliased()against a_orm.with_polymorphic()
construct, which would previously produce an alias of the base mapped
class only, discarding the polymorphic selectable and additional mappers.
The new construct now retains these, so that criteria against subclass
attributes and theinnerjoinandselectableparameters of
_orm.with_polymorphic()take effect, and subclass columns are loaded
up front. The SQL rendered for these constructs now includes the
polymorphic selectable.This change is also backported to: 2.0.53
-
[orm] [bug] Fixed issue where using
_orm.aliased()with both the
_orm.aliased.nameand_orm.aliased.flat
parameters, against an entity that is mapped to a join which includes
anonymously named aliases, would embed the anonymous name symbol within
the names generated for each element of the join, producing unusable
SQL. An anonymously named element of the join is now aliased
anonymously.This change is also backported to: 2.0.53
References: #13583
sql
-
[sql] [bug] Fixed issue where calling
_sql.CTE.alias()on a_sql.CTE
that was itself produced by_sql.CTE.alias()would render SQL that
referred to the name of the intermediate alias, which is not present in
the WITH clause, rather than to the name of the original CTE, producing
invalid SQL.This change is also backported to: 2.0.53
References: #13583
postgresql
-
[postgresql] [usecase] PostgreSQL index reflection now reports so-called "invalid" indexes with
postgresql_invalid=Truein the Inspector'sdialect_options.
ReflectedIndexobjects expose this state in the new
read-onlyIndex.reflect_only_elementsmapping, separate from
DDL options. Added new markerDialectKWArgConst.REFLECTED_ONLY
which may be used by third party dialects for similar features within
theDefaultDialect.construct_argumentsregistry. Pull request
courtesy Max Azatian.References: #13577
mssql
-
[mssql] [bug] Adjusted the
mssql+mssqlpythondialect for mssql-python version 1.15,
which now binds all PythonDecimalvalues asSQL_NUMERIC. The
conversion of very large and very smallDecimalvalues to strings,
carried over from the pyodbc dialect, is no longer applied for this
version of the driver, as the string values would otherwise be converted
by SQL Server to the narrower numeric type of otherDecimalvalues in
the same statement, such as within a multiple-row INSERT, raising an
arithmetic overflow error.References: #13585
-
[mssql] [bug] Fixed issue where
Dialect.dbapi_versionwould raise
AttributeErrorfor themssql+mssqlpythondialect, as the version
was looked up using aversionattribute that is not present on the
mssql_pythonmodule, rather than__version__.References: #13585
oracle
-
[oracle] [usecase] Added support for oracledb's new
terminate()feature, which allows for
clean termination of an Oracle database connection in an asyncio context
where the connection's state has fallen out of the event loop, and needs to
be garbage collected. The feature is enabled automatically when using
oracledb 26.0.0 or greater.This change is also backported to: 2.0.53
References: #13578
2.0.54
2.0.54
Released: September 15, 2026
platform
-
[platform] [change] Binary wheels are no longer built for Python 3.7. PyPI now rejects wheel
files whose filename does not begin with the normalized project name, and
the packaging tools that can be installed on Python 3.7 do not produce
such a filename. As a result, SQLAlchemy 2.0.44 was the last release to
publish Python 3.7 wheels to PyPI, and releases 2.0.45 and later have
been available on Python 3.7 only as a source distribution; the wheel
builds for Python 3.7 are now removed. Python 3.7 remains supported by
the 2.0 series. -
[platform] [bug] Fixed issue where the Cython extensions were compiled without the
freethreading_compatibledirective, so that they did not declare
themselves as safe to run without the GIL. On a free-threaded Python
interpreter such as Python 3.13t or 3.14t, importing SQLAlchemy would
cause the interpreter to re-enable the GIL, emitting a
RuntimeWarning. The directive is now set when building for Python
3.13 and above, and a test has been added which confirms that importing
SQLAlchemy on a free-threaded build does not enable the GIL.References: #13592
2.0.53
2.0.53
Released: September 14, 2026
orm
-
[orm] [bug] Fixed issue where an expression passed to
_orm.with_expression()
that embedded a_sql.select(), such as a correlated
_sql.exists(), would fail to populate the attribute correctly on
the second and subsequent executions of an otherwise identical
statement, when the_orm.query_expression()attribute was loaded
by a relationship loader that emits a second query, i.e.
_orm.selectinload(),_orm.lazyload()or
_orm.immediateload().References: #13560
-
[orm] [bug] Fixed memory issue where mapped classes, along with their
Tableand_orm.Mapperobjects, would not be garbage
collected after the_orm.registryin which they were mapped had
been disposed and dereferenced. The issue would occur for mappings that
made use of_orm.relationship()together with constructs such as an
Indexestablished against an ORM-annotated expression.References: #13566
-
[orm] [bug] Fixed issue where pickling an ORM object that had an instance level lazy
loader established, such as when the_orm.raiseload()option is used,
would emit a spurious warning regarding the loader containing additional
criteria, if the object had itself been unpickled from a previous
serialization. This would occur for objects that cross more than one
serialization boundary, such as when using multiprocessing.References: #13574
-
[orm] [bug] Fixed issue where calling
_orm.aliased()against an existing
_orm.aliased()construct, without passing an explicit selectable,
would disregard the selectable of the existing construct and produce an
alias of the mapped table instead, if that selectable were anything other
than a table or a plain subquery, leading to incorrect results and/or
non-working queries.This includes
_orm.aliased()against a_orm.with_polymorphic()
construct, which would previously produce an alias of the base mapped
class only, discarding the polymorphic selectable and additional mappers.
The new construct now retains these, so that criteria against subclass
attributes and theinnerjoinandselectableparameters of
_orm.with_polymorphic()take effect, and subclass columns are loaded
up front. The SQL rendered for these constructs now includes the
polymorphic selectable. -
[orm] [bug] Fixed issue where using
_orm.aliased()with both the
_orm.aliased.nameand_orm.aliased.flat
parameters, against an entity that is mapped to a join which includes
anonymously named aliases, would embed the anonymous name symbol within
the names generated for each element of the join, producing unusable
SQL. An anonymously named element of the join is now aliased
anonymously.References: #13583
engine
-
[engine] [bug] [asyncio] [pool] Fixed issue where a DBAPI connection would be left open and unreachable
if an exception were raised within the_events.PoolEvents.connect()
or_events.PoolEvents.first_connect()event handlers, which is
whereDialect.initialize()runs. The connection had been created
but not yet associated with anything that could close it, so it was
neither returned to the pool nor closed. For an asyncio driver in
particular this could leak a server-side session for the life of the
process, as the garbage collector is not able to close a connection that
requires the event loop.References: #13548
sql
-
[sql] [bug] Fixed issue where calling
_sql.CTE.alias()on a_sql.CTE
that was itself produced by_sql.CTE.alias()would render SQL that
referred to the name of the intermediate alias, which is not present in
the WITH clause, rather than to the name of the original CTE, producing
invalid SQL.References: #13583
schema
-
[schema] [bug] Fixed issue where reflecting a table with a foreign key constraint that
names the same source column more than once, such asFOREIGN KEY (a, a) REFERENCES r (b, c)which is accepted by backends such as PostgreSQL,
would raiseArgumentErrorand fail the reflection of the entire
table. AsForeignKeyConstrainthas no representation for this
form, the constraint is now skipped with a warning, in the same way as one
that names columns which are not present in the table, so that the
remainder of the table is still reflected.Note that in the SQLAlchemy 2.1 series, full support for reflecting
and constructing foreign key constraints with duplicated source columns
has been added, with no warnings or skips.References: #13525
postgresql
-
[postgresql] [bug] Fixed bug in
_reflection.Inspector.get_schema_names()for
PostgreSQL where the query used to exclude system schemas relied on
NOT LIKE 'pg_%', which treats the underscore as a SQLLIKE
wildcard rather than a literal character. This caused user-created
schemas that happen to start with "pg" followed by any other
character, such aspgsqlorpgstats, to be silently excluded
along with actual system schemas likepg_catalog. Pull request
courtesy Evan Rusackas.References: #13472
mysql
-
[mysql] [bug] Ensure that CREATE TABLE DDL statements for MySQL and MariaDB dialects
render the table options in a deterministic order. Previously the order
could change depending on the Python seed.References: #13523
sqlite
-
[sqlite] [usecase] [reflection] Implemented
_engine.Inspector.get_table_options()for the SQLite
dialect, which previously raisedNotImplementedError. The method
returns thesqlite_with_rowidandsqlite_strictdialect options
for a table that was created using theWITHOUT ROWIDand / or
STRICTkeywords. As a result, these keywords are now also present in
_schema.Table.kwargsfor a_schema.Tablethat is
reflected using autoload, so that a table which is recreated from its
reflected form, such as by Alembic's batch migration mode, renders the
keywords again rather than silently dropping them.References: #13543
-
[sqlite] [bug] [reflection] Fixed issue in SQLite reflection where the name of a
PRIMARY KEY,
UNIQUEorFOREIGN KEYconstraint would be reflected asNoneif
theCONSTRAINT <name>clause were separated from the keyword that
follows it by a newline rather than by spaces. As SQLite stores the
CREATE TABLEstatement as it was originally typed, this affected
tables created from hand-written DDL that spans multiple lines. The
regular expressions used to recover constraint names, as well as the
ON UPDATE/ON DELETE,DEFERRABLEandINITIALLYoptions of
a foreign key constraint, now accept any whitespace between tokens.References: #13528
-
[sqlite] [bug] [reflection] Reworked the regular expression that parses
FOREIGN KEYconstraints
during SQLiteCREATE TABLEreflection so that the referred column list
is matched unambiguously avoiding potential exponential backtracking.
Pull request courtesy of Javid Khan.References: #13530
mssql
-
[mssql] [bug] [reflection] Fixed issue in SQL Server reflection where
TEXTandNTEXTcolumns
would be reflected with a spurious length of 16 and 8, respectively. These
are unlengthed LOB datatypes; the value originates from the
sys.columns.max_lengthcolumn, which reports the size of the in-row LOB
pointer rather than a character length for these types. The reflected
_mssql.TEXTand_mssql.NTEXTtypes now have alength
ofNone, so that a reflected table emits valid DDL when re-created,
which previously failed with "Cannot specify a column width on data type
text". Pull request courtesy Sam Debruyn.References: #13451
oracle
-
[oracle] [usecase] Added support for oracledb's new
terminate()feature, which allows for
clean termination of an Oracle database connection in an asyncio context
where the connection's state has fallen out of the event loop, and needs to
be garbage collected. The feature is enabled automatically when using
oracledb 26.0.0 or greater.References: #13578
tests
-
[tests] [bug] Altered the dialect reflection test
test_check_constraint_parenthesized_expressions()so that it does not
convert the reflected constraint to lowercase, which interferes with some
third party dialect's representation of reflected check constraints.References: #13521
misc
-
[bug] [installation] Added the
AUTHORSfile to the set of license files included in the
built wheel, where previously only theLICENSEfile was present. As
the text ofLICENSErefers toAUTHORSfor the list of copyright
holders, the reference would not resolve for tools that inspect an
installed distribution.R...