Releases: sqlalchemy/sqlalchemy
Release list
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...
2.1.0rc2
2.1.0rc2
Released: September 8, 2026
orm
-
[orm] [usecase] The collection of per-mapper / per-table binds established by the
_orm.Session.bindsparameter, as well as by the
_orm.Session.bind_mapper()and_orm.Session.bind_table()
methods, is now available publicly as_orm.Session.binds; the
collection was previously stored under a private, name-mangled attribute.
It is an immutable dictionary, and is replaced rather than mutated when a
new bind is added, so a reference to it will not observe subsequent
changes. The collection is also proxied by
_orm.scoping.scoped_session.References: #13563
-
[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().This change is also backported to: 2.0.53
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.This change is also backported to: 2.0.53
References: #13566
orm declarative
-
[bug] [orm declarative] Fixed memory issue where a declarative class making use of the
__declare_first__()or__declare_last__()hooks could never be
garbage collected, as each such class established its own permanent
_orm.Mapper-wide event listener referring to it. These hooks
are now invoked by_orm.registry-localRegistryEvents
listeners which refer to the classes weakly, and which are invoked only
for the_orm.registrybeing configured. As the hooks are now
located as the class is added to the_orm.registry, they also
take effect for classes mapped using
_orm.registry.map_imperatively().References: #9147
engine
- [engine] [changed] Improved the implementation of
_pool.Poolto use
weakref.finalize()instead of aweakref.ref()finalizer to handle
GC cleanup of non-detached, pooled connections that were not explicitly
closed.
asyncio
-
[asyncio] [usecase]
_asyncio.AsyncSession.bindand
_asyncio.AsyncSession.bindsare now always present and are
derived from the underlying_asyncio.AsyncSession.sync_session,
translated back into their asyncio equivalents. Added
_asyncio.AsyncSession.get_async_bind(),
_asyncio.AsyncSession.bind_mapper()and
_asyncio.AsyncSession.bind_table()as the asyncio-facing
counterparts to the_orm.Sessionmethods of the same name.
Both attributes, along with
_asyncio.AsyncSession.get_async_bind(), are proxied by
_asyncio.async_scoped_session.References: #13563
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.This change is also backported to: 2.0.53
References: #13543
-
[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.This change is also backported to: 2.0.53
References: #13530
2.1.0rc1
2.1.0rc1
Released: August 31, 2026
platform
-
[platform] [change] Python 3.11 or above is now required; support for Python 3.10 is dropped,
in addition to the drop of versions Python 3.9, 3.8 and 3.7 introduced
in 2.1.0b1. Python 3.10 reaches EOL in October of 2026, so dropping
support now gives the SQLAlchemy 2.1 series an extra year of space to
remain on current Python versions. -
[platform] [bug] Python 3.15 support has been added and tested, including minimal changes
for full compatibility.This change is also backported to: 2.0.52
References: #13477
orm
-
[orm] [usecase] Improved the error message raised when a
Sessionis used
inside a context manager after the transaction has been rolled back due
to an exception. TheInvalidRequestErrornow includes the original
exception that triggered the rollback, making it clearer why the
transaction is no longer active. Pull request courtesy Ilan Keshet.References: #11297
-
[orm] [usecase] Improved error messages raised when ORM loader strategy options cannot be
applied to a query. Messages now render the offending option in a
user-friendly form such asjoinedload(User.orders)rather than exposing
internal class and path representations, and the "does not apply to root
entities" message now includes the option that triggered the error. The
same user-friendly rendering is also applied to the "conflicting loader
strategy" message and to theof_type()representation in "does not
link" messages. Originating pull request courtesy Jan Vollmer.References: #12398
-
[orm] [usecase] Python source generated at runtime is now compiled against a descriptive
filename which is registered with thelinecachemodule, so that
generated functions appearing on a stack trace render with their source
rather than as an opaqueFile "<string>"frame. This allows tools
likepdbandinspect.getsource()to work with these generated
source blocks as well. The new feature is applied to the instrumentation
applied to an ORM object's__init__method, as well as throughout
SQLAlchemy functions that are internally instrumented.References: #13505
-
[orm] [usecase] When a subclass overrides a
_orm.validates()method using the
same method name as the parent class, only the subclass validator is
now invoked for instances of the subclass. The subclass validator
may callsuper()to also invoke the parent class validator.
Previously, the parent validator was always used regardless of
whether the subclass provided an override. Pull request courtesy
Indivar Mishra.References: #2943
-
[orm] [bug] Fixed a result-column misalignment bug in ORM-enabled UPDATE statements
wheresynchronize_session="fetch"is in use, either explicitly or
because the statement uses constructs such as CTEs that implicitly select
for it. Columns in rows returned by.returning()could be returned
under incorrect keys (e.g.row[SomeClass.a]returning the value of
a different column), a problem most likely to manifest under concurrent
workloads. ORM DELETE statements were not affected.This change is also backported to: 2.0.52
References: #13439
-
[orm] [bug] Fixed bug where a failed
_orm.Session.bulk_insert_mappings(),
_orm.Session.bulk_update_mappings()or
_orm.Session.bulk_save_objects()call could leave the
_orm.Sessionpermanently in a "flushing" state, such as when the
transaction could not be begun because a previous flush had left it
needing a rollback. Unlike_orm.Session.flush(), the bulk methods
set the internal flushing flag and began the transaction outside of the
try/finallyblock that resets it, so that neither
_orm.Session.rollback()nor_orm.Session.close()would clear
it, and every subsequent flush would raiseInvalidRequestError: Session is already flushing. Pull request courtesy Hamody We.This change is also backported to: 2.0.52
References: #13485
-
[orm] [bug] Fixed issue where unpickling an ORM object that were loaded using loader
options making use of wildcard tokens, such as_orm.load_only()or
_orm.raiseload()with"*", would fail withKeyErroror
IndexErrorif the process doing the unpickling had not yet constructed
a loader path making use of that same token. This would typically be
observed when the object were unpickled in a separate process, such as
with thespawnorforkservermultiprocessing start methods, the
latter of which became the default on POSIX platforms as of Python 3.14.
The internal collection of these tokens is now established up front, so
that it is identical in every process.This change is also backported to: 2.0.52
References: #13493
-
[orm] [bug] Fixed issue where a string ending in
"*"passed to a
_orm.Loadstrategy method, such as
Load(A).joinedload("bs.*"), would bypass the check which rejects
string attribute names in loader options, silently producing a loader
path that matched nothing. Such a string now raises
ArgumentErrorwith the same message given for any other
string attribute name. The bare wildcard"*", as in
Load(A).lazyload("*"), continues to be accepted.This change is also backported to: 2.0.52
References: #13493
-
[orm] [bug] Calling
_orm.aliased()against a_sql.select()or
_sql.union()/_sql.CompoundSelectconstruct, which
previously failed with an obscureAttributeErrorregarding a missing
.mapperattribute, now raises when using SQLAlchemy 2.1, and emits a
deprecation warning under SQLAlchemy 2.0 as it coerces the construct into a
subquery instead. This matches the behavior of other similar implicit
SELECT-to-FROM coercions. Pull request courtesy Rens Groothuijsen.This change is also backported to: 2.0.52
References: #6274
-
[orm] [bug] [regression] Fixed regression caused by the dataclasses change in #12168 where
passing_orm.relationship.default_factoryaslistto a
relationship that used the_orm.WriteOnlyMappedor
_orm.DynamicMappedannotation would raise an error at mapper
configuration time, as these relationships have nocollection_class.
listis now accepted for these relationships, which behave the same
as ordinary collections in this regard; the factory itself is never
invoked, and a newly constructed object begins with an empty
collection. Documentation is added atwrite_only_dataclasses
illustrating the use of write only and dynamic relationships with ORM
mapped dataclasses.References: #13227
-
[orm] [bug] Fixed long-standing issue where an object that was loaded at more than one
path within a single query, such as when a chain of_orm.joinedload()
options leads back to an entity that was also loaded at the top level of
the query, would retain the loader options of whichever path the query
happened to see last, which varied with the loader strategy in use. The
options an object retains are applied to all forms of :term:lazy loading
for that object, so an otherwise identical set of options could behave
differently depending on the loader strategy. The shallowest path is now
favored, which is deterministic.Unknown interpreted text role "term".
References: #13507
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.This change is also backported to: 2.0.53
References: #13548
sql
-
[sql] [usecase] Added new methods
_sql.Exists.with_hint()and
_sql.Exists.with_statement_hint(), which apply a table hint or a
statement hint to the SELECT statement that's enclosed by the EXISTS
expression, in the same way as_sql.Select.with_hint()and
_sql.Select.with_statement_hint(). As ORM constructs such as
_orm.PropComparator.any()and_orm.PropComparator.has()
produce an_sql.Existsobject, hints may now be applied to the
subqueries which these constructs generate. Pull request courtesy
Abhinav Gorrepati.References: #8311
-
[sql] [performance] Improved the performance of SQL cache key generation by moving the
traversal into the Cython extension modules. The set of attributes that
participate in the cache k...
2.0.52
2.0.52
Released: August 11, 2026
platform
-
[platform] [bug] Python 3.15 support has been added and tested, including minimal changes
for full compatibility.References: #13477
orm
-
[orm] [bug] Fixed a result-column misalignment bug in ORM-enabled UPDATE statements
wheresynchronize_session="fetch"is in use, either explicitly or
because the statement uses constructs such as CTEs that implicitly select
for it. Columns in rows returned by.returning()could be returned
under incorrect keys (e.g.row[SomeClass.a]returning the value of
a different column), a problem most likely to manifest under concurrent
workloads. ORM DELETE statements were not affected.References: #13439
-
[orm] [bug] Fixed bug where a failed
_orm.Session.bulk_insert_mappings(),
_orm.Session.bulk_update_mappings()or
_orm.Session.bulk_save_objects()call could leave the
_orm.Sessionpermanently in a "flushing" state, such as when the
transaction could not be begun because a previous flush had left it
needing a rollback. Unlike_orm.Session.flush(), the bulk methods
set the internal flushing flag and began the transaction outside of the
try/finallyblock that resets it, so that neither
_orm.Session.rollback()nor_orm.Session.close()would clear
it, and every subsequent flush would raiseInvalidRequestError: Session is already flushing. Pull request courtesy Hamody We.References: #13485
-
[orm] [bug] Fixed issue where unpickling an ORM object that were loaded using loader
options making use of wildcard tokens, such as_orm.load_only()or
_orm.raiseload()with"*", would fail withKeyErroror
IndexErrorif the process doing the unpickling had not yet constructed
a loader path making use of that same token. This would typically be
observed when the object were unpickled in a separate process, such as
with thespawnorforkservermultiprocessing start methods, the
latter of which became the default on POSIX platforms as of Python 3.14.
The internal collection of these tokens is now established up front, so
that it is identical in every process.References: #13493
-
[orm] [bug] Fixed issue where a string ending in
"*"passed to a
_orm.Loadstrategy method, such as
Load(A).joinedload("bs.*"), would bypass the check which rejects
string attribute names in loader options, silently producing a loader
path that matched nothing. Such a string now raises
ArgumentErrorwith the same message given for any other
string attribute name. The bare wildcard"*", as in
Load(A).lazyload("*"), continues to be accepted.References: #13493
-
[orm] [bug] Calling
_orm.aliased()against a_sql.select()or
_sql.union()/_sql.CompoundSelectconstruct, which
previously failed with an obscureAttributeErrorregarding a missing
.mapperattribute, now raises when using SQLAlchemy 2.1, and emits a
deprecation warning under SQLAlchemy 2.0 as it coerces the construct into a
subquery instead. This matches the behavior of other similar implicit
SELECT-to-FROM coercions. Pull request courtesy Rens Groothuijsen.References: #6274
orm declarative
-
[bug] [orm declarative] Fixed issue where using PEP 593
Annotatedwrapping a PEP 695
typealias, such asAnnotated[SomeTypeAlias, mapped_column()],
would crash withAttributeError: __value__. The internal
is_pep695()check incorrectly identified theAnnotatedtype as a
PEP 695 type alias due to a quirk inAnnotated.__origin__returning
the first type argument rather thanAnnotateditself.References: #13386
sql
-
[sql] [bug] Fixed issue where
_sql.Select.get_final_froms()would emit a
deprecation warning when the statement made use of the PostgreSQL-specific
expression argument to_sql.Select.distinct(); the same spurious
warning would be emitted when stringifying such a statement without
explicitly using a PostgreSQL dialect. The fix ensures that this 1.4-era
warning is suppressed under both 2.0 and 2.1.Note that under SQLAlchemy 2.1, passing an expression to
_sql.Select.distinct()is deprecated overall, and is replaced by a
new PostgreSQL-specific construct (see #12342).References: #13396
-
[sql] [bug] Fixed an issue in
Numericwhere the
Numeric.decimal_return_scaleparameter was ignored when the
DBAPI does not support native decimal objects (i.e.
dialect.supports_native_decimalisFalse). In this path the result
processor was computing the conversion scale from
Numeric.scaledirectly, bypassing
Numeric.decimal_return_scaleentirely. The behavior now
matchesFloat, which already used the correct
_effective_decimal_return_scaleproperty. Pull request courtesy Kadir
Can Ozden.References: #13424
-
[sql] [bug] Added auditing to the test suite which exercises the literal execute
processors across all datatypes and dialects to ensure that string input is
either appropriately rejected or correctly escaped. Literal execute
processors are invoked when thebindparam.literal_execute
parameter is used with an explicitbindparam()object, which
overrides DBAPI-native bind handling to render the value inline with the
statement instead. Datatypes that were updated include the originally
reported SQL ServerUuid/UNIQUEIDENTIFIERrendering which now
escapes properly, theJSONPATHtype that's currently
PostgreSQL-only, and a full family of numeric types stemming from the
_types.Floatand_types.Numericbases which now coerce
the value to a number, rejecting non-numeric input. Thanks to Javid Khan
for helping to identify the issue.References: #13448
schema
-
[schema] [bug] Fixed an issue where
_schema.Table.to_metadata()reused column
default and on-update objects, causing the defaults on the original
columns to refer to the copied columns. Default generators, including
sequences, and server-side defaults are now copied and remain associated
with their respective columns and metadata collections. Applications that
inspected these objects will now see distinct defaults on the copied table
instead of the objects owned by the original table. Pull request courtesy
Goutam Adwant.References: #13481
postgresql
-
[postgresql] [bug] [reflection] Fixed reflection of PostgreSQL CHECK constraints where an expression made
up of multiple parenthesized sub-expressions, such as(x IS NULL OR y IS NULL) AND (x IS NULL OR y IS NULL), would have its leading and trailing
parentheses incorrectly stripped, producing an unbalanced and
syntactically invalid reflected expression. Pull request courtesy
Shaurya Singh.References: #13157
-
[postgresql] [bug] Fixed bug in the PostgreSQL dialect where a single quote in a sequence,
table, or schema name, such as one supplied via aschema_translate_map
or an explicitSequence, could result in a malformed
nextval()statement. The quote is now properly escaped. Pull request
courtesy dxbjavid.References: #13429
sqlite
-
[sqlite] [bug] Reworked the regular expression that detects inline
UNIQUEcolumn
constraints during SQLiteCREATE TABLEreflection so that the
whitespace separating a column's type from a following clause is matched
unambiguously. The previous pattern had three overlapping quantifiers
that could each consume a space character, so a column definition
carrying a long run of whitespace in the stored schema made
_reflection.Inspector.get_unique_constraints()spend cubic time
backtracking before returning. Fix courtesy of Javid Khan.References: #13419
mssql
-
[mssql] [bug] Tightened the construction of the ODBC connection string in the pyodbc
connector (as well as the mssql-python connector in 2.1) so that the
driver name, the names of pass-through connection parameters, and values
containing}are brace-quoted. Previously a}in the driver name
or in a pass-through value, or a;in the name of a pass-through
parameter, could close the surrounding token early and allow the
remainder of the string to be interpreted as additional connection
attributes. Pull request courtesy dxbjavid.References: #13380
tests
-
[tests] [bug] Fixed class-scoped pytest fixtures that were defined as instance methods
usingself, which is deprecated as of pytest 9.1 and will be removed in
pytest 10. Fixtures are now decorated with a compatibility@classmethod
decorator and useclsas the first parameter.References: #13392
2.1.0b3
2.1.0b3
Released: June 27, 2026
orm
-
[orm] [feature] Added
selectinload.chunksizeparameter toselectinload()
allowing users to configure the number of primary keys sent per IN clause
when loading relationships. Pull request courtesy bekapono.References: #11450
-
[orm] [usecase] The
populate_existingexecution option is now honored when passed in the
Session.get.execution_optionsdict by the method
Session.get()and analogous in other session kinds. The current
Session.get.populate_existingparameter will takes precedence if
specified, overriding the value of the execution options.References: #10610
-
[orm] [usecase] Updated the attribute
_orm.ORMExecuteState.user_defined_optionsto
include options that were added to the statement before calling
Select.with_only_columns()or_orm.Query.with_entities().References: #13309
-
[orm] [usecase] [performance] Optimized
_orm.selectinload()to skip the.unique()call on inner
result sets when no nested_orm.joinedload()on a collection is
present. The uniquing pass is only required when a joined eager load
inflates rows due to a one-to-many or many-to-many JOIN; in the common case
of a leaf selectin load, rows are already unique by construction and the
per-row hashing overhead can be avoided. As a side effect,yield_per
set in ado_orm_executeevent for a_orm.selectinload()
relationship load no longer raisesInvalidRequestErrorwhen no nested
collection joinedload is in effect, since.unique()is no longer called
in that path. Pull request courtesy Oliver Parker.References: #13339
-
[orm] [usecase] Session level
_orm.Session.execution_optionsnow take
effect for Core level SQL emitted by unit of work operations, in
addition to their existing use within ORM statement executions.
This is to provide for Core options such as
_engine.Connection.execution_options.schema_translate_map
to be applicable to aSessionoverall.References: #13346
-
[orm] [performance] ORM result row fetching now processes rows as plain tuples rather than
constructingRowobjects, as ORM loaders use position-based
access and do not require theRowinterface.Row
construction is still used when engine-level debug logging is enabled so
that individual rows can be logged. Benchmarks show a 3-16% improvement in
ORM entity load times depending on query shape. Pull request courtesy
Oliver Parker.References: #13363
-
[orm] [performance] Improved performance of
_orm.selectinload()and
_orm.subqueryload()result handling:- in selectinloader, the primary key columns used to correlate related rows are now selected directly rather than being wrapped in a `Bundle`, and are read from positional slices of each result row. This removes the per-row `Row` construction that the `Bundle` introduced, including for the common single-column primary key case. - removed use of `groupby()` + `lambda` against `Row` objects in subqueryloader; rows are converted to plain tuples and the result lists are built via `append()`. - many-to-one selectinload reads foreign key values directly from the parent instance dictionary when present, falling back to attribute-level access only for expired or deferred attributes.Pull request courtesy Oliver Parker.
References: #13363
-
[orm] [performance] The
selectinload()loader strategy now selects theomit_join
optimization for many-to-many non-self-referential relationships, reducing
the number of joins in the secondary SELECT by selecting from the secondary
table directly rather than joining back to the parent entity.omit_join
is enabled automatically when the join condition determines that the
secondary table's foreign keys fully cover the parent's primary key. As
always,omit_joincan be disabled by setting
relationship.omit_jointoFalse. Pull request courtesy
bekapono.References: #5987
-
[orm] [bug] Fixed issue where the declarative class registry would not consider
class-levelMetaDataobjects set on abstract mixin classes when
resolving string-based table references inrelationship()
configurations. The registry now uses the same metadata resolution logic
as table creation, first checking for a class-specificmetadata
attribute before falling back toregistry.metadata.References: #13291
-
[orm] [bug] Fixed issue where the
_engine.Result.unique()filter was not properly
validated against the_engine.Result.yield_per()method when both
were called as methods on the result object, such as
result.unique().yield_per(N)orresult.yield_per(N).unique(). The
uniquing filter was previously only checked whenyield_perwas set via
_engine.Connection.execution_options.yield_per. Since these two
features are fundamentally incompatible for ORM results, an
InvalidRequestErroris now raised in all cases.References: #13293
-
[orm] [bug] A warning is now emitted when a Declarative attribute name is named
metadataorregistry. Previously, no warning was emitted for
registry, and using the namemetadatawould raise an
InvalidRequestError. Since these names can be used for attributes
that are mapped as backrefs or using imperative mappings, usage
under Declarative has been relaxed formetadatabut also warns
for both names as they may have unintended interactions with the
Declarative reserved names.References: #13333
-
[orm] [bug] Fixed issue where the declarative class resolver would not consider
theMetaData.schemadefault schema when resolving a
string table name for therelationship.secondaryparameter
as well as within string-based
relationship.primaryjoinand
relationship.secondaryjoinexpressions. The resolution now
matches the behavior ofForeignKey, where an unqualified
table name is implicitly resolved under the default schema. A
deprecation warning is emitted when an unqualified name resolves to a
:data:.BLANK_SCHEMAtable in aMetaDatathat has a default
schema set, as this implicit resolution will change in a future version.Unknown interpreted text role "data".
References: #8068
engine
-
[engine] [bug] Expanded try/except error handling to encompass the
_events.ConnectionEvents.before_cursor_execute()and
_events.ConnectionEvents.after_cursor_execute()event hooks, so that
exceptions raised within these hooks, includingBaseException
subclasses such asasyncio.CancelledError, are properly handled via the
error handling path used for DBAPI errors. This ensures proper connection
invalidation and pool notification when exit-type exceptions are raised in
event hooks. As part of this change, DBAPI errors raised from within these
event hooks will now be wrapped as SQLAlchemy exceptions.References: #13381
-
[engine] [reflection] Removed the legacy
include_columnskey from the dictionary returned
by the index reflection methods of some dialects.
This information is now part of thedialect_optionsdictionary under the key
{dialect_name}_include, such aspostgresql_includeormssql_include.References: #13350
sql
-
[sql] [usecase] Added
_sql.Delete.using(), allowing explicit FROM expressions such as
joins to be rendered in backend-specific multiple-table DELETE forms
including MySQL/MariaDBDELETE .. USING. Pull request courtesy
cjc0013.References: #8130
-
[sql] [bug] Fixed issue where negation of comparison expressions involving
func.any(),func.all(), andfunc.some()SQL functions would
incorrectly flip the comparison operator (e.g.=to!=) rather
than wrapping the expression withNOT. These functions are now
registered as collection aggregate functions that prevent operator
flipping on negation, consistent with the behavior of the standalone
_expression.any_()and_expression.all_()constructs.References: #13343
postgresql
-
[postgresql] [usecase] Changed the default backslash escape value in the PostgreSQL dialect to
Falseto align it with the default value of
standard_conforming_strings=on. This change should not affect most users
since the value is set at driver initialization on first connect.References: #13268
mysql
- [mysql] [bug] Improved the regular expression used to parse index
COMMENTclauses
in MySQLSHOW CREATE TABLEreflection to use an unambiguous
single-quoted-string pattern; the previous pattern was theoretically
subject to backtracking on malformed input, though such input ...
2.0.51
2.0.51
Released: June 15, 2026
orm
-
[orm] [bug] Fixed issue where
_orm.subqueryload()combined with
PropComparator.of_type()andPropComparator.and_()would
silently drop the additional filter criteria, causing all related objects
to be loaded instead of only those matching the filter. The
LoaderCriteriaOptionwas being constructed against the base
entity rather than the effective entity indicated by
PropComparator.of_type(). Pull request courtesy Arya Rizky.References: #13207
-
[orm] [bug] Fixed bug where a failure during
tpc_prepare()within
_orm.Session.commit()for a two-phase session would raise
IllegalStateChangeErrorinstead of the original database
exception. The internal_prepare_impl()method's error handler
was unable to invoke_orm.SessionTransaction.rollback()due
to a state-change guard, preventing proper cleanup and masking the
underlying error.References: #13356
engine
-
[engine] [bug] Fixed issue where
Result.freeze()would lose track of ambiguous
column names present in the originalCursorResult, causing
key-based access on the thawed result to silently return a value instead of
raisingInvalidRequestError. The
SimpleResultMetaDatanow accepts and propagates ambiguous key
information so that frozen, thawed, and pickled results raise consistently
for duplicate column names. Pull request courtesy Saurabh Kohli.References: #9427
sql
-
[sql] [bug] Fixed issue where
_sql.StatementLambdaElementwould proxy
attribute access through the cached "expected" expression rather than the
resolved expression, causing stale closure-bound parameter values to be
used when a lambda statement was extended with non-lambda criteria such as
an additional.where()clause. Courtesy cjc0013.References: #10827
postgresql
-
[postgresql] [bug] Repaired bug introduced in #13229 where a two-phase
transaction recovery would not return the correct transaction
identifier when generating the identifiers using thexid()
method of the psycopg connection.References: #13355
-
[postgresql] [bug] Fixed regular expression in the pure Python hstore result processor,
used whenuse_native_hstore=Falseis set, which could hang on
malformed hstore text containing unterminated quoted segments with
backslashes. Pull request courtesy dxbjavid.References: #13370