2.1 Changelog¶
2.1.0rc1¶
Released: August 31, 2026platform¶
[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 as
joinedload(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 the
linecachemodule, 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
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.See also
References: #2943
[orm] [bug] ¶
Fixed a result-column misalignment bug in ORM-enabled UPDATE statements where
synchronize_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
Session.bulk_insert_mappings(),Session.bulk_update_mappings()orSession.bulk_save_objects()call could leave theSessionpermanently in a “flushing” state, such as when the transaction could not be begun because a previous flush had left it needing a rollback. UnlikeSession.flush(), the bulk methods set the internal flushing flag and began the transaction outside of thetry/finallyblock that resets it, so that neitherSession.rollback()norSession.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
load_only()orraiseload()with"*", would fail withKeyErrororIndexErrorif 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 aLoadstrategy method, such asLoad(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 raisesArgumentErrorwith the same message given for any other string attribute name. The bare wildcard"*", as inLoad(A).lazyload("*"), continues to be accepted.This change is also backported to: 2.0.52
References: #13493
[orm] [bug] ¶
Calling
aliased()against aselect()orunion()/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
relationship.default_factoryaslistto a relationship that used theWriteOnlyMappedorDynamicMappedannotation 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 at Using Write Only Relationships with ORM 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
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 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.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
PoolEvents.connect()orPoolEvents.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
Exists.with_hint()andExists.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 asSelect.with_hint()andSelect.with_statement_hint(). As ORM constructs such asPropComparator.any()andPropComparator.has()produce anExistsobject, 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 key for a particular construct, along with the handler that applies to each one, is now resolved once at class setup time into a structure that the compiled traversal consumes directly, so that the chain of identity comparisons that formerly rediscovered this per attribute, per cache key is no longer run at all. Benchmarks against a range of Core and ORM statements show cache key generation running approximately 1.5 to 2.2 times faster in a build with the Cython extensions compiled, and approximately 1.05 to 1.2 times faster in a pure Python build, with the generated cache keys themselves unchanged.
References: #13506
[sql] [bug] ¶
Fixed an issue in
Numericwhere theNumeric.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 fromNumeric.scaledirectly, bypassingNumeric.decimal_return_scaleentirely. The behavior now matchesFloat, which already used the correct_effective_decimal_return_scaleproperty. Pull request courtesy Kadir Can Ozden.This change is also backported to: 2.0.52
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 the
bindparam.literal_executeparameter 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 theFloatandNumericbases which now coerce the value to a number, rejecting non-numeric input. Thanks to Javid Khan for helping to identify the issue.This change is also backported to: 2.0.52
References: #13448
[sql] [bug] ¶
Fixed issue where two bound parameters whose names differ only in the characters listed in
SQLCompiler.bindname_escape_characters, such as those generated for columns named"a.b"and"a_b", would be rendered using the same name in the compiled statement, as those characters are escaped only as the parameter is rendered. The value for one of the two parameters was then silently used for both, affecting SELECT criteria as well as the VALUES and SET clauses of INSERT and UPDATE statements, where a value could be written to the wrong column. Escaped parameter names are now disambiguated against the names already in use. The.keyof eachBindParameteris unaffected, so parameter dictionaries passed by the caller continue to be keyed as before.References: #13534
[sql] [bug] ¶
Fixed issue where an empty string passed to
IdentifierPreparer.quote(), such as the name of aTableconstructed with a blank name, would raiseIndexErrorrather than being rendered. An empty identifier is now always quoted. While a blank name is not a legal identifier on most backends, SQLite accepts one, so such a table may be delivered by reflection; a table with a blank name can now be used in SELECT, INSERT, UPDATE, DELETE and DDL statements.References: #13535
schema¶
[schema] [usecase] ¶
ForeignKeyConstraintnow accepts a constraint which names the same local column more than once, such asFOREIGN KEY (a, a) REFERENCES r (b, c). This form is valid SQL and constrains the referenced row so that two of its columns are equal; it previously raisedArgumentError. Such a constraint now emits and reflects like any other composite foreign key; the workaround added in 2.0 for #13525, which skipped such a constraint during reflection, is removed as it is no longer needed. As part of this change, the check that the number of constrained columns matches the number of referenced columns no longer counts distinct column names, so that a genuine mismatch such asForeignKeyConstraint(["x", "x"], ["r.b"]), which was formerly accepted and silently dropped a column, is now rejected.References: #13526
[schema] [usecase] ¶
Added new
ForeignKeyaccessorsForeignKey.target_tokens,ForeignKey.target_columnandForeignKey.target_table_key, as well as new ForeignKey-related datastructureForeignKeyTarget.ForeignKeyTargetis now accepted as a constructor argument as well. SeeForeignKeyfor new datamembers and usage patterns.References: #13538
[schema] [usecase] ¶
Added
Dialect.dbapi_version, a standardized accessor for the version of the DBAPI module in use by a dialect, in contrast toDialect.server_version_infowhich refers to the database server. The implementation onDefaultDialectmakes use of a new per-dialect methodDialect.retrieve_dbapi_version()in order to retrieve the version from the DBAPI module and return it as aVersionInfoobject, which is a tuple subclass with additional properties; third-party dialects should also implement theDialect.retrieve_dbapi_version()method.[schema] [performance] ¶
Created new reflection method
Inspector.has_multi_table()to check the existence of multiple tables at once, allowing for better performance when checking many tables. Like the other “multi” reflection methods, the default dialect offers a default implementation that just call the single method in a loop. Backends that wish to take advantage of this new method can implement it in their dialects. The PostgreSQL, Oracle and SQL Server dialects have been updated to use this new method. The implementation ofMetaData.create_all()has been updated to make use of this new method to check the existence of the tables, reducing the number of round trips to the database when creating many tables.References: #13311
[schema] [bug] ¶
Fixed an issue where
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.This change is also backported to: 2.0.52
References: #13481
[schema] [bug] ¶
Fixed issue where a
ForeignKeywhich refers to a table or column whose name contains a dot would be interpreted incorrectly, as the dotted string form of the target could not be told apart from the separator between a schema, table and column name. Foreign key targets are now tracked as their individual schema, table and column names throughout, and are no longer derived by splitting a dotted string.References: #13538
[schema] [deprecated] ¶
ForeignKey.target_fullnameis now a legacy accessor, and raisesInvalidRequestErrorwhen the target has no unambiguous dotted string form, which is the case when the target table or column name contains a dot, or when a schema name is present with no column name. No SQLAlchemy internals make use of the attribute any longer; new code should useForeignKey.target_tokens.References: #13538
postgresql¶
[postgresql] [usecase] ¶
Added
postgresql_withsupport toCreateViewfor specifying PostgreSQL view options such assecurity_invoker,security_barrier, andcheck_option, rendered as aWITH (...)clause between the view name and theASkeyword. Additionally, thepostgresql_withparameter accepted byTableandIndexnow correctly renders Python boolean values astrue/false(lowercase), andNonevalues as the parameter name alone without an= valueportion. Pull request courtesy alphavector.[postgresql] [usecase] ¶
The PostgreSQL dialect now reflects the schema of a schema-qualified column or
DOMAINcollation, populating the newString.collation_schema/DOMAIN.collation_schemaparameters so that reflected DDL round-trips exactly. The schema is omitted from the reflected value when the collation is visible on the currentsearch_pathwithout qualification.See also
References: #6511
[postgresql] [usecase] ¶
Added a new parameter
String.collation_schema, as well asDOMAIN.collation_schemaandColumnOperators.collate.collation_schema, allowing a PostgreSQL schema-qualified collation name to be specified explicitly, rather than embedding the schema name within thecollationstring itself, which previously rendered incorrectly. As part of this change, collation name rendering across DDL and thecollate()construct now consistently uses the dialect’s identifier preparer for quoting, rather than several separate, inconsistent hand-quoting code paths; as a side effect, simple lowercase collation names such as"utf8"are no longer unconditionally quoted in generated DDL.See also
References: #9693
[postgresql] [bug] ¶
Fixed bug in
Inspector.get_schema_names()for PostgreSQL where the query used to exclude system schemas relied onNOT LIKE 'pg_%', which treats the underscore as a SQLLIKEwildcard 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.This change is also backported to: 2.0.53
References: #13472
[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.This change is also backported to: 2.0.52
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 a
schema_translate_mapor an explicitSequence, could result in a malformednextval()statement. The quote is now properly escaped. Pull request courtesy dxbjavid.This change is also backported to: 2.0.52
References: #13429
[postgresql] [bug] ¶
Fixed issue in the asyncpg dialect where the version of the
asyncpgDBAPI would always be reported as(99, 99, 99), as the version was looked up on the dialect’s DBAPI wrapper module rather than on theasyncpgmodule itself.
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.
This change is also backported to: 2.0.53
References: #13523
[mysql] [bug] ¶
Fixed issue where the version of the DBAPI reported by the mysqldb and pymysql dialects was incorrect. Current mysqlclient releases publish
MySQLdb.version_infoand no version string at all, so no version was reported; pymysql publishes__version__andversion_infoas mysqlclient compatibility values, so the version reported for pymysql was that of the mysqlclient release it emulates, e.g.(2, 2, 8)rather than(1, 2, 0).
sqlite¶
[sqlite] [usecase] ¶
Added support for multiple
ON CONFLICTclauses within a single statement for the SQLiteinsert()construct; theInsert.on_conflict_do_update()andInsert.on_conflict_do_nothing()methods may now each be invoked more than once against the same construct, where the clauses render in the order in which they were established and are evaluated by SQLite in that order. As SQLite allows only the lastON CONFLICTclause to omit its conflict target, aInsert.on_conflict_do_nothing()call that omitsInsert.on_conflict_do_nothing.index_elementsmust be the last clause established. Documentation is added at Specifying Multiple ON CONFLICT Clauses. Pull request courtesy Diemid Berozkin.References: #13113
[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 theCREATE 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 theON UPDATE/ON DELETE,DEFERRABLEandINITIALLYoptions of a foreign key constraint, now accept any whitespace between tokens.This change is also backported to: 2.0.53
References: #13528
[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 madeInspector.get_unique_constraints()spend cubic time backtracking before returning. Fix courtesy of Javid Khan.This change is also backported to: 2.0.52
References: #13419
[sqlite] [bug] ¶
Added a warning for query string arguments that are passed to a SQLite URL without the
uri=trueargument also being present, and which are not accepted by thesqlite3driver itself. SQLite URI arguments such asmodeorcachetake effect only when URI mode is in use; without it they were previously discarded silently, so that a URL such assqlite:///file:mydb?mode=memorywould connect to a file on disk namedfile:mydb. Arguments intended for the driver itself may be passed using thecreate_engine.connect_argsparameter.References: #13433
[sqlite] [bug] [documentation] ¶
Corrected the SQLite documentation regarding shared cache memory databases, which incorrectly indicated that the named form
sqlite:///file:mydb?mode=memory&cache=shared&uri=truemakes use ofQueuePool; a single-connection pool is used for this form. Documentation has also been added noting that a shared cache database exists only for as long as at least one connection to it remains open, so that ordinary pool operations such asEngine.dispose()or use ofcreate_engine.pool_recyclewill discard its contents.References: #13433
[sqlite] [deprecated] ¶
Deprecated the selection of a single-connection pool class, i.e.
SingletonThreadPoolfor pysqlite orStaticPoolfor aiosqlite, based on the presence of themode=memoryquery string argument in a SQLite URL. Pool selection for SQLite is intended to be based on the database name alone, where only:memory:or an empty database name indicate a memory database; interpreting the query string additionally requires that assumptions be made regarding whether or not the resulting database can be shared among multiple connections. In a future release, such URLs will make use ofQueuePoolorAsyncAdaptedQueuePoolas would any other URL. This notably includes the shared cache formsqlite:///file:mydb?mode=memory&cache=shared&uri=true, for which a queue pool is in fact the appropriate class, as a shared cache database supports multiple concurrent connections, whereas a single-connection pool causes such connections to share one transaction state. Applications that rely upon the present behavior should indicate the intended pool using thecreate_engine.poolclassparameter. Pull request courtesy Itachi-0xAI.References: #13433
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 thesys.columns.max_lengthcolumn, which reports the size of the in-row LOB pointer rather than a character length for these types. The reflectedTEXTandNTEXTtypes now have alengthofNone, 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.This change is also backported to: 2.0.53
References: #13451
[mssql] [bug] ¶
Improved disconnect detection for the
mssql+mssqlpythondialect. Connection-level failures such as a dropped or reset network connection are now recognized by consulting thedriver_errorattribute of the exception, in addition to the message-based checks that were already in place, so that the affected connection is invalidated and the pool “pre ping” feature is able to recycle it. Pull request courtesy Sam Debruyn.References: #13441
oracle¶
[oracle] [bug] ¶
Updated the oracledb async dialect where the async cursor adapter invoked
__enter__()rather than__aenter__()on the underlying cursor. While these are equivalent in oracledb itself, the correct async form is now used for correctness. AsAsyncCursor.__aenter__()was added in oracledb 2.0.1, the minimum supported oracledb version is now 2.0.1, declared via theoracle-oracledbextra. Pull request courtesy AVRC26.References: #13420
[oracle] [bug] ¶
Fixed issue in the Oracle dialects where a
JSONvalue would be returned as an undecoded string for any JSON expression that is not a JSON column, such as a bound parameter, as well as for textual constructs with positional columns, such astext()combined withTextClause.columns().References: #13479
tests¶
[tests] [usecase] ¶
The version specifications used by testing exclusions such as
testing.fails_if("+asyncmy<0.2.13")now support a driver name, in which case the comparison is against the version of the DBAPI rather than that of the database server. Previously this form raisedAssertionError: DBAPI version specs not supported yet.[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.This change is also backported to: 2.0.53
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.This change is also backported to: 2.0.53
References: #13518
[misc] [bug] ¶
Allowed the inspection registry to replace an existing registration with a reloaded callable from the same module and name. This avoids an assertion failure for tooling that unloads and reloads SQLAlchemy modules while still rejecting conflicting registrations. Pull request courtersy w-Jessamine.
References: #10748
2.1.0b3¶
Released: June 27, 2026orm¶
[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 theSession.get.execution_optionsdict by the methodSession.get()and analogous in other session kinds. The currentSession.get.populate_existingparameter will takes precedence if specified, overriding the value of the execution options.References: #10610
[orm] [usecase] ¶
Updated the attribute
ORMExecuteState.user_defined_optionsto include options that were added to the statement before callingSelect.with_only_columns()orQuery.with_entities().References: #13309
[orm] [usecase] ¶
Session level
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 asConnection.execution_options.schema_translate_mapto be applicable to aSessionoverall.References: #13346
[orm] [performance] [usecase] ¶
Optimized
selectinload()to skip the.unique()call on inner result sets when no nestedjoinedload()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_perset in ado_orm_executeevent for aselectinload()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] [performance] ¶
ORM result row fetching now processes rows as plain tuples rather than constructing
Rowobjects, as ORM loaders use position-based access and do not require theRowinterface.Rowconstruction 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
selectinload()andsubqueryload()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-rowRowconstruction that theBundleintroduced, including for the common single-column primary key case.removed use of
groupby()+lambdaagainstRowobjects in subqueryloader; rows are converted to plain tuples and the result lists are built viaappend().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_joinoptimization 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_joinis 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 settingrelationship.omit_jointoFalse. Pull request courtesy bekapono.References: #5987
[orm] [bug] ¶
Fixed issue where
subqueryload()combined withPropComparator.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. TheLoaderCriteriaOptionwas being constructed against the base entity rather than the effective entity indicated byPropComparator.of_type(). Pull request courtesy Arya Rizky.This change is also backported to: 2.0.51
References: #13207
[orm] [bug] ¶
Fixed bug where a failure during
tpc_prepare()withinSession.commit()for a two-phase session would raiseIllegalStateChangeErrorinstead of the original database exception. The internal_prepare_impl()method’s error handler was unable to invokeSessionTransaction.rollback()due to a state-change guard, preventing proper cleanup and masking the underlying error.This change is also backported to: 2.0.51
References: #13356
[orm] [bug] ¶
Fixed issue where using
joinedload()withPropComparator.of_type()targeting a joined-table subclass combined withPropComparator.and_()referencing a column on that subclass would generate invalid SQL, where the subclass column was not adapted to the subquery alias. Pull request courtesy Joaquin Hui Gomez.This change is also backported to: 2.0.50
References: #13203
[orm] [bug] ¶
Fixed issue where the presence of a
SessionEvents.do_orm_execute()event hook would cause internal execution options such asyield_perand loader-specific state from the firstorm_pre_session_execpass to leak into the second pass, leading to errors when using relationship loaders such asselectinload()andimmediateload(). The execution options passed to the second compilation pass are now based on the original options plus only the explicit updates made viaORMExecuteState.update_execution_options()within the event hook.This change is also backported to: 2.0.50
References: #13301
[orm] [bug] ¶
Fixed issue where using
with_polymorphic()on a leaf class (a subclass with no further descendants) or a non-inherited class would fail with anAttributeErrorwhen used in an ORM statement, due toconfigure_mappers()not being triggered implicitly. The fix ensures thatAliasedInspparticipates in the_post_inspecthook, triggering mapper configuration during ORM statement compilation.This change is also backported to: 2.0.50
References: #13319
[orm] [bug] ¶
Fixed issue where the declarative class registry would not consider class-level
MetaDataobjects 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-specificmetadataattribute before falling back toregistry.metadata.References: #13291
[orm] [bug] ¶
Fixed issue where the
Result.unique()filter was not properly validated against theResult.yield_per()method when both were called as methods on the result object, such asresult.unique().yield_per(N)orresult.yield_per(N).unique(). The uniquing filter was previously only checked whenyield_perwas set viaConnection.execution_options.yield_per. Since these two features are fundamentally incompatible for ORM results, anInvalidRequestErroris 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 forregistry, 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 the
MetaData.schemadefault schema when resolving a string table name for therelationship.secondaryparameter as well as within string-basedrelationship.primaryjoinandrelationship.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 aBLANK_SCHEMAtable in aMetaDatathat has a default schema set, as this implicit resolution will change in a future version.References: #8068
orm declarative¶
[bug] [orm declarative] ¶
Fixed issue where using PEP 593
Annotatedwrapping a PEP 695typealias, such asAnnotated[SomeTypeAlias, mapped_column()], would crash withAttributeError: __value__. The internalis_pep695()check incorrectly identified theAnnotatedtype as a PEP 695 type alias due to a quirk inAnnotated.__origin__returning the first type argument rather thanAnnotateditself.This change is also backported to: 2.0.52
References: #13386
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. TheSimpleResultMetaDatanow accepts and propagates ambiguous key information so that frozen, thawed, and pickled results raise consistently for duplicate column names. Pull request courtesy Saurabh Kohli.This change is also backported to: 2.0.51
References: #9427
[engine] [bug] ¶
Expanded try/except error handling to encompass the
ConnectionEvents.before_cursor_execute()andConnectionEvents.after_cursor_execute()event hooks, so that exceptions raised within these hooks, includingBaseExceptionsubclasses 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
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
Select.get_final_froms()would emit a deprecation warning when the statement made use of the PostgreSQL-specific expression argument toSelect.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
Select.distinct()is deprecated overall, and is replaced by a new PostgreSQL-specific construct (see #12342).This change is also backported to: 2.0.52
References: #13396
[sql] [bug] ¶
Fixed issue where
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.This change is also backported to: 2.0.51
References: #10827
[sql] [bug] ¶
Fixed issue where floor division (
//) between aFloatorNumericnumerator and anIntegerdenominator would omit theFLOOR()SQL wrapper on dialects whereDialect.div_is_floordivisTrue(the default, including PostgreSQL and SQLite).FLOOR()is now applied if either the denominator or the numerator is a non-integer, so that expressions such asfloat_col // int_colrender asFLOOR(float_col / int_col)instead of the incorrectfloat_col / int_col. Pull request courtesy r266-tech.This change is also backported to: 2.0.50
References: #10528
[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 standaloneany_()andall_()constructs.References: #13343
postgresql¶
[postgresql] [usecase] ¶
Changed the default backslash escape value in the PostgreSQL dialect to
Falseto align it with the default value ofstandard_conforming_strings=on. This change should not affect most users since the value is set at driver initialization on first connect.References: #13268
[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 the
xid()method of the psycopg connection.This change is also backported to: 2.0.51
References: #13355
[postgresql] [bug] ¶
Fixed regular expression in the pure Python hstore result processor, used when
use_native_hstore=Falseis set, which could hang on malformed hstore text containing unterminated quoted segments with backslashes. Pull request courtesy dxbjavid.This change is also backported to: 2.0.51
References: #13370
[postgresql] [bug] ¶
Fixed issue where the asyncpg driver could throw an insufficiently-handled exception
InternalClientErrorunder some circumstances, leading to connections not being properly marked as invalidated.This change is also backported to: 2.0.50
References: #13241
[postgresql] [bug] ¶
Fixed issue where the
ExcludeConstraintconstruct did not correctly forward theExcludeConstraint.infoparameter to the superclass, causing user-defined metadata to be lost. Pull request courtesy Wiktor Byrka.This change is also backported to: 2.0.50
References: #13317
mysql¶
[mysql] [bug] [reflection] ¶
Narrowed the scope of the internal workaround for MySQL bugs #88718 and #96365 so that it is only applied where needed: MySQL 8.0.1 through 8.0.13 (where bug 88718 is present), and on systems with
lower_case_table_names=2(where bug 96365 applies, typically macOS). Previously the workaround was applied unconditionally for all MySQL 8.0+ versions, which caused aKeyErrorduring foreign key reflection when the database user lacked SELECT privileges on referred tables.This change is also backported to: 2.0.50
References: #13243
[mysql] [bug] ¶
Fixed issue in aiomysql and asyncmy dialects that appears as of using pymysql 1.2.0; the dialects were not properly taking into account logic that detects the argument signature of pymysql’s
ping()method which was added as part of #10492.This change is also backported to: 2.0.50
References: #13306
[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 is not producible by MySQL itself. Fix courtesy of Javid Khan.References: #13393
sqlite¶
[sqlite] [feature] ¶
Added
JSONBtype for SQLite’s binary JSON storage format, available as of SQLite version 3.45.0. Values are stored via thejsonb()SQL function and retrieved viajson(), while the Python-side behavior remains identical toJSON. Pull request courtesy Shamil Abdulaev.See also
References: #13260
mssql¶
[mssql] [performance] [reflection] ¶
Implemented native multi-table reflection methods for the SQL Server dialect, providing
MSDialect.get_multi_columns(),MSDialect.get_multi_pk_constraint(),MSDialect.get_multi_foreign_keys(),MSDialect.get_multi_indexes()andMSDialect.get_multi_table_comment(). Previously the SQL Server dialect relied on the default dialect default implementation which calls the per-table methods in a loop; the new implementations issue a single bulk query per object type against thesys.*catalog views, avoiding the per-table round trips. The single-table reflection methods are now thin wrappers over the multi-table ones, matching the pattern used by the PostgreSQL and Oracle dialects. Pull request courtesy Gaurav Sharma.References: #8430
[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.This change is also backported to: 2.0.52
References: #13380
tests¶
[tests] [bug] ¶
Fixed class-scoped pytest fixtures that were defined as instance methods using
self, which is deprecated as of pytest 9.1 and will be removed in pytest 10. Fixtures are now decorated with a compatibility@classmethoddecorator and useclsas the first parameter.This change is also backported to: 2.0.52
References: #13392
2.1.0b2¶
Released: April 16, 2026orm¶
[orm] [usecase] ¶
The
metadata,type_annotation_map, orregistrycan now be set up in a declarative base also via a mixin class, not only by directly setting them on the subclass like before. The declarative class setup now usesgetattr()to look for these attributes, instead of relying only on the class__dict__.References: #13198
[orm] [bug] ¶
Fixed issue where
Session.get()would bypass the identity map and emit unnecessary SQL whenwith_for_update=Falsewas passed, rather than treating it equivalently to the default ofNone. Pull request courtesy of Joshua Swanson.This change is also backported to: 2.0.49
References: #13176
[orm] [bug] ¶
Fixed issue where chained
joinedload()options would not be applied correctly when the final relationship in the chain is declared on a base mapper and accessed through a subclass mapper in awith_polymorphic()query. The path registry now correctly computes the natural path when a property declared on a base class is accessed through a path containing a subclass mapper, ensuring the loader option can be located during query compilation.This change is also backported to: 2.0.49
References: #13193
[orm] [bug] [inheritance] ¶
Fixed issue where using
Load.options()to apply a chained loader option such asjoinedload()orselectinload()withPropComparator.of_type()for a polymorphic relationship would not generate the necessary clauses for the polymorphic subclasses. The polymorphic loading strategy is now correctly propagated when using a call such asjoinedload(A.b).options(joinedload(B.c.of_type(poly)))to match the behavior of direct chaining e.g.joinedload(A.b).joinedload(B.c.of_type(poly)).This change is also backported to: 2.0.49
References: #13202
[orm] [bug] [inheritance] ¶
Fixed issue where using chained loader options such as
selectinload()afterjoinedload()withPropComparator.of_type()for a polymorphic relationship would not properly apply the chained loader option. The loader option is now correctly applied when using a call such asjoinedload(A.b.of_type(poly)).selectinload(poly.SubClass.c)to eagerly load related objects.This change is also backported to: 2.0.49
References: #13209
[orm] [bug] ¶
Fixed issue when using ORM mappings with Python 3.14’s PEP 649 feature that no longer requires “future annotations”, where the ORM’s introspection of the
__init__method of mapped classes would fail if non-present identifiers in annotations were present. The vendoredgetfullargspec()method has been amended to useFormat.FORWARDREFunder Python 3.14 to prevent resolution of names that aren’t present.This change is also backported to: 2.0.47
References: #13104
engine¶
[engine] [usecase] ¶
The connection object returned by
Engine.raw_connection()now supports the context manager protocol, automatically returning the connection to the pool when exiting the context.This change is also backported to: 2.0.47
References: #13116
[engine] [bug] ¶
Fixed a critical issue in
Enginewhere connections created in conjunction with theDialectEvents.do_connect()event listeners would receive shared, mutable collections for the connection arguments, leading to a variety of potential issues including unlimited growth of the argument list as well as elements within the parameter dictionary being shared among concurrent connection calls. In particular this could impact do_connect routines making use of complex mutable authentication structures.This change is also backported to: 2.0.48
References: #13144
sql¶
[sql] [usecase] ¶
Added new parameter
over.excludetoover()and related methods, enabling SQL standard frame exclusion clausesEXCLUDE CURRENT ROW,EXCLUDE GROUP,EXCLUDE TIES,EXCLUDE NO OTHERSin window functions. Pull request courtesy of Varun Chawla.References: #11671
[sql] [usecase] ¶
The
ColumnCollectionclass hierarchy has been refactored to allow column names such asadd,remove,update,extend, andclearto be used without conflicts.ColumnCollectionis now an abstract base class, with mutation operations moved toWriteableColumnCollectionandDedupeColumnCollectionsubclasses. TheReadOnlyColumnCollectionexposed as attributes such asTable.cno longer includes mutation methods that raisedNotImplementedError, allowing these common column names to be accessed naturally, e.g.table.c.add,table.c.remove,table.c.update, etc.[sql] [bug] ¶
A warning is emitted when using the standalone
distinct()function in aselect()columns list outside of an aggregate function; this function is not intended as a replacement for the use ofSelect.distinct(). Pull request courtesy bekapono.References: #11526
[sql] [bug] ¶
Improved the ability for
TypeDecoratorto produce a correctrepr()for “schema” types such asEnumandBoolean. This is mostly to support the Alembic autogenerate use case so that custom types render with relevant arguments present. Improved the architecture used byTypeEngineto producerepr()strings to be more modular for compound types likeTypeDecorator.References: #13140
schema¶
[schema] [usecase] ¶
Most
FromClausesubclasses are now generic onTypedColumnssubclasses, that can be used to type theirFromClause.ccollection. This applied toTable,Join,Subquery,CTEand more.References: #13085
[schema] [bug] ¶
Amended the
repr()output forEnumso that theMetaDatais not shown in the output, as this interferes with Alembic-autogenerated forms of this type which should be inheriting theMetaDataof the parent table in the migration script.References: #10604
typing¶
[typing] [bug] ¶
Fixed a typing issue where the typed members of
funcwould return the appropriate class of the same name, however this creates an issue for typecheckers such as Zuban and pyrefly that assume PEP 749 style typechecking even if the file states that it’s a PEP 563 file; they see the returned name as indicating the method object and not the class object. These typecheckers are actually following along with an upcoming test harness that insists on PEP 749 style name resolution for this case unconditionally. Since PEP 749 is the way of the future regardless, differently-named type aliases have been added for these return types.This change is also backported to: 2.0.49
References: #13167
[typing] [bug] ¶
Fixed issue in new PEP 646 support for result sets where an issue in the mypy type checker prevented “scalar” methods including
Connection.scalar(),Result.scalar(),Session.scalar(), as well as async versions of these methods from applying the correct type to the scalar result value, when the columns in the originatingselect()were typed asAny. Pull request courtesy Yurii Karabas.References: #13091
[typing] [bug] ¶
Improved typing of
JSONas well as dialect specific variants likeJSONto include generic capabilities, so that the types may be parameterized to indicate any specific type of contents expected, e.g.JSONB[list[str]]().References: #13131
postgresql¶
[postgresql] [bug] ¶
Improve handling of two phase transaction identifiers for PostgreSQL when the identifier is provided by the user. As part of this change the psycopg dialect was updated to use the DBAPI two phase transaction API instead of executing the SQL directly.
This change is also backported to: 2.0.50
References: #13229
[postgresql] [bug] ¶
Fixed regular expression used when reflecting foreign keys in PostgreSQL to support escaped quotes in table names. Pull request courtesy of Austin Graham
This change is also backported to: 2.0.49
References: #10902
[postgresql] [bug] ¶
Fixed an issue in the PostgreSQL dialect where foreign key constraint reflection would incorrectly swap or fail to capture
onupdateandondeletevalues when these clauses appeared in a different order than expected in the constraint definition. This issue primarily affected PostgreSQL-compatible databases such as CockroachDB, which may returnON DELETEbeforeON UPDATEin the constraint definition string. The reflection logic now correctly parses both clauses regardless of their ordering.This change is also backported to: 2.0.47
References: #13105
[postgresql] [bug] ¶
Fixed issue in the “Insert Many Values” Behavior for INSERT statements feature where using PostgreSQL’s
ON CONFLICTclause withInsert.returning.sort_by_parameter_orderenabled would generate invalid SQL when the insert used an implicit sentinel (server-side autoincrement primary key). The generated SQL would incorrectly declare a sentinel counter column in theimp_sentable alias without providing corresponding values in theVALUESclause, leading to aProgrammingErrorindicating column count mismatch. The fix allows batch execution mode whenembed_values_counteris active, as the embedded counter provides the ordering capability needed even with upsert behaviors, rather than unnecessarily downgrading to row-at-a-time execution.This change is also backported to: 2.0.47
References: #13107
[postgresql] [bug] ¶
Fixed issue where
Insert.on_conflict_do_update()parameters were not respecting compilation options such asliteral_binds=True. Pull request courtesy Loïc Simon.This change is also backported to: 2.0.47
References: #13110
[postgresql] [bug] ¶
Fixed issue where
Insert.on_conflict_do_update()using parametrized bound parameters in theset_clause would fail when used with executemany batching. For dialects that use theuse_insertmanyvalues_wo_returningoptimization (psycopg2), insertmanyvalues is now disabled when there is an ON CONFLICT clause. For cases with RETURNING, row-at-a-time mode is used when the SET clause contains parametrized bindparams (bindparams that receive values from the parameters dict), ensuring each row’s parameters are correctly applied. ON CONFLICT statements using expressions likeexcluded.<column>continue to batch normally.This change is also backported to: 2.0.47
References: #13130
mysql¶
[mysql] [bug] ¶
Fixed issue where DDL compilation options were registered to the hard-coded dialect name
mysql. This made it awkward for MySQL-derived dialects like MariaDB, StarRocks, etc. to work with such options when different sets of options exist for different platforms. Options are now registered under the actual dialect name, and a fallback was added to help avoid errors when an option does not exist for that dialect.To maintain backwards compatibility, when using the MariaDB dialect with the options
mysql_with_parserormysql_usingwithout also specifying the correspondingmariadb_prefixed options, a deprecation warning will be emitted. Themysql_prefixed options will continue to work during the deprecation period. Users should update their code to additionally specifymariadb_with_parserandmariadb_usingwhen using themariadb://dialect, or specify both options to support both dialects.Pull request courtesy Tiansu Yu.
This change is also backported to: 2.0.47
References: #13134
sqlite¶
[sqlite] [bug] ¶
Escape key and pragma values when utilizing the pysqlcipher dialect.
This change is also backported to: 2.0.50
References: #13230
[sqlite] [bug] ¶
Fixed issue where
Insert.on_conflict_do_update()parameters were not respecting compilation options such asliteral_binds=True. Pull request courtesy Loïc Simon.This change is also backported to: 2.0.47
References: #13110
[sqlite] [bug] ¶
Fixed issue where
Insert.on_conflict_do_update()using parametrized bound parameters in theset_clause would fail when used with executemany batching. Row-at-a-time mode is now used for ON CONFLICT statements with RETURNING that contain parametrized bindparams, ensuring each row’s parameters are correctly applied. ON CONFLICT statements using expressions likeexcluded.<column>continue to batch normally.This change is also backported to: 2.0.47
References: #13130
mssql¶
[mssql] [feature] ¶
Added support for the
mssql-pythondriver, Microsoft’s official Python driver for SQL Server.See also
mssql-python - Documentation for the mssql-python dialect
References: #12869
[mssql] [usecase] ¶
Enhanced the
aioodbcdialect to expose thefast_executemanyattribute of the pyodbc cursor. This allows thefast_executemanyparameter to work with themssql+aioodbcdialect. Pull request courtesy Georg Sieber.This change is also backported to: 2.0.49
References: #13152
[mssql] [usecase] ¶
Remove warning for SQL Server dialect when a new version is detected. The warning was originally added more than 15 years ago due to an unexpected value returned when using an old version of FreeTDS. The assumption is that since then the issue has been resolved, so make the SQL Server dialect behave like the other ones that don’t have an upper bound check on the version number.
This change is also backported to: 2.0.49
References: #13185
[mssql] [bug] [reflection] ¶
Fixed regression from version 2.0.42 caused by #12654 where the updated column reflection query would receive SQL Server “type alias” names for special types such as
sysname, whereas previously the base name would be received (e.g.nvarcharforsysname), leading to warnings that such types could not be reflected and resulting inNullType, rather than the expectedNVARCHARfor a type likesysname. The column reflection query now joinssys.typesa second time to look up the base type when the user type name is not present inMSDialect.ischema_names, and both names are checked inMSDialect.ischema_namesfor a match. Pull request courtesy Carlos Serrano.This change is also backported to: 2.0.49
oracle¶
[oracle] [feature] ¶
Added support for the
JSONdatatype when using the Oracle database with the oracledb dialect. JSON values are serialized and deserialized using configurable strategies that accommodate Oracle’s native JSON type available as of Oracle 21c. Pull request courtesy Abdallah Alhadad.See also
JSON- Oracle-specific JSON class that includes implementation and platform notes.References: #10375
[oracle] [bug] ¶
Fixed issue in Oracle dialect where the
RAWdatatype would not reflect the length parameter. Pull request courtesy Daniel Sullivan.This change is also backported to: 2.0.49
References: #13150
2.1.0b1¶
Released: January 21, 2026platform¶
[platform] [feature] ¶
Free-threaded Python versions are now supported in wheels released on Pypi. This integrates with overall free-threaded support added as part of #12881 for the 2.0 and 2.1 series, which includes new test suites as well as a few improvements to race conditions observed under freethreading.
References: #12881
[platform] [change] ¶
The
greenletdependency used for asyncio support no longer installs by default. This dependency does not publish wheel files for every architecture and is not needed for applications that aren’t using asyncio features. Use thesqlalchemy[asyncio]install target to include this dependency.References: #10197
[platform] [change] ¶
Updated the setup manifest definition to use PEP 621-compliant pyproject.toml. Also updated the extra install dependency to comply with PEP-685. Thanks for the help of Matt Oberle and KOLANICH on this change.
[platform] [change] ¶
Python 3.10 or above is now required; support for Python 3.9, 3.8 and 3.7 is dropped as these versions are EOL.
Note
as of 2.0.0rc1 3.10 is also dropped
..seealso:
:ref:`change_python_versions`
orm¶
[orm] [feature] ¶
The
relationship.back_populatesargument torelationship()may now be passed as a Python callable, which resolves to either the direct linked ORM attribute, or a string value as before. ORM attributes are also accepted directly byrelationship.back_populates. This change allows type checkers and IDEs to confirm the argument forrelationship.back_populatesis valid. Thanks to Priyanshu Parikh for the help on suggesting and helping to implement this feature.References: #10050
[orm] [feature] ¶
Added new hybrid method
hybrid_property.bulk_dml()which works in a similar way ashybrid_property.update_expression()for bulk ORM operations. A user-defined class method can now populate a bulk insert mapping dictionary using the desired hybrid mechanics. New documentation is added showing how both of these methods can be used including in combination with the newfrom_dml_column()construct.See also
References: #12496
[orm] [feature] ¶
Added new parameter
composite.return_none_ontocomposite(), which allows control over if and when this composite attribute should resolve toNonewhen queried or retrieved from the object directly. By default, a composite object is always present on the attribute, including for a pending object which is a behavioral change since 2.0. Whencomposite.return_none_onis specified, a callable is passed that returns True or False to indicate if the given arguments indicate the composite should be returned as None. This parameter may also be set automatically when ORM Annotated Declarative is used; if the annotation is given asMapped[SomeClass|None], acomposite.return_none_onrule is applied that will returnNoneif all contained columns are themselvesNone.References: #12570
[orm] [feature] ¶
Added support for per-session execution options that are merged into all queries executed within that session. The
Session,sessionmaker,scoped_session,AsyncSession, andasync_sessionmakerconstructors now accept anSession.execution_optionsparameter that will be applied to all explicit query executions (e.g. usingSession.execute(),Session.get(),Session.scalars()) for that session instance.References: #12659
[orm] [feature] ¶
Session autoflush behavior has been simplified to unconditionally flush the session each time an execution takes place, regardless of whether an ORM statement or Core statement is being executed. This change eliminates the previous conditional logic that only flushed when ORM-related statements were detected, which had become difficult to define clearly with the unified v2 syntax that allows both Core and ORM execution patterns. The change provides more consistent and predictable session behavior across all types of SQL execution.
References: #9809
[orm] [feature] ¶
Added
RegistryEventsevent class that allows event listeners to be established on aregistryobject. The new class provides three events:RegistryEvents.resolve_type_annotation()which allows customization of type annotation resolution that can supplement or replace the use of theregistry.type_annotation_mapdictionary, including that it can be helpful with custom resolution for complex types such as those of PEP 695, as well asRegistryEvents.before_configured()andRegistryEvents.after_configured(), which are registry-local forms of the mapper-wide version of these hooks.References: #9832
[orm] [usecase] ¶
The
Session.flush.objectsparameter is now deprecated.References: #10816
[orm] [usecase] ¶
Added the utility method
Session.merge_all()andSession.delete_all()that operate on a collection of instances.References: #11776
[orm] [usecase] ¶
Added support for using
with_expression()to populate aquery_expression()attribute that is also configured as thepolymorphic_ondiscriminator column. The ORM now detects when a query expression column is serving as the polymorphic discriminator and updates it to use the column provided viawith_expression(), enabling polymorphic loading to work correctly in this scenario. This allows for patterns such as where the discriminator value is computed from a related table.References: #12631
[orm] [usecase] ¶
Added default implementations of
ColumnOperators.desc(),ColumnOperators.asc(),ColumnOperators.nulls_first(),ColumnOperators.nulls_last()tocomposite()attributes, by default applying the modifier to all contained columns. Can be overridden using a custom comparator.References: #12769
[orm] [usecase] ¶
The
aliased()object now emits warnings when an attribute is accessed on an aliased class that cannot be located in the target selectable, for those cases where thealiased()is against a different FROM clause than the regular mapped table (such as a subquery). This helps users identify cases where column names don’t match between the aliased class and the underlying selectable. Whenaliased.adapt_on_namesisTrue, the warning suggests checking the column name; whenFalse, it suggests using theadapt_on_namesparameter for name-based matching.References: #12838
[orm] [usecase] ¶
Improvements to the use case of using Declarative Dataclass Mapping with intermediary classes that are unmapped. As was the existing behavior, classes can subclass
MappedAsDataclassalone without a declarative base to act as mixins, or along with a declarative base as well as__abstract__ = Trueto define an abstract base. However, the improved behavior scans ORM attributes likemapped_column()in this case to create correctdataclasses.field()constructs based on their arguments, allowing for more natural ordering of fields without dataclass errors being thrown. Additionally, added a newunmapped_dataclass()decorator function, which may be used to create unmapped mixins in a mapped hierarchy that is using themapped_dataclass()decorator to create mapped dataclasses.References: #12854
[orm] [usecase] ¶
Added
DictBundleas a subclass ofBundlethat returnsdictobjects.References: #12960
[orm] [change] ¶
A sweep through class and function names in the ORM renames many classes and functions that have no intent of public visibility to be underscored. This is to reduce ambiguity as to which APIs are intended to be targeted by third party applications and extensions. Third parties are encouraged to propose new public APIs in Discussions to the extent they are needed to replace those that have been clarified as private.
References: #10497
[orm] [change] ¶
The
first_initORM event has been removed. This event was non-functional throughout the 1.4 and 2.0 series and could not be invoked without raising an internal error, so it is not expected that there is any real-world use of this event hook.References: #10500
[orm] [change] ¶
Removed legacy signatures dating back to 0.9 release from the
SessionEvents.after_bulk_update()andSessionEvents.after_bulk_delete().References: #10721
[orm] [changed] ¶
The “non primary” mapper feature, long deprecated in SQLAlchemy since version 1.3, has been removed. The sole use case for “non primary” mappers was that of using
relationship()to link to a mapped class against an alternative selectable; this use case is now suited by the Relationship to Aliased Class feature.References: #12437
[orm] [bug] ¶
The
relationship.secondaryparameter no longer uses Pythoneval()to evaluate the given string. This parameter when passed a string should resolve to a table name that’s present in the localMetaDatacollection only, and never needs to be any kind of Python expression otherwise. To use a real deferred callable based on a name that may not be locally present yet, use a lambda instead.References: #10564
[orm] [bug] ¶
Fixed issue where joined eager loading would fail to use the “nested” form of the query when GROUP BY or DISTINCT were present if the eager joins being added were many-to-ones, leading to additional columns in the columns clause which would then cause errors. The check for “nested” is tuned to be enabled for these queries even for many-to-one joined eager loaders, and the “only do nested if it’s one to many” aspect is now localized to when the query only has LIMIT or OFFSET added.
References: #11226
[orm] [bug] ¶
Revised the set “binary” operators for the association proxy
set()interface to correctly raiseTypeErrorfor invalid use of the|,&,^, and-operators, as well as the in-place mutation versions of these methods, to match the behavior of standard Pythonset()as well as SQLAlchemy ORM’s “instrumented” set implementation.References: #11349
[orm] [bug] ¶
A significant behavioral change has been made to the behavior of the
mapped_column.defaultandrelationship.defaultparameters, as well as therelationship.default_factoryparameter with collection-based relationships, when used with SQLAlchemy’s Declarative Dataclass Mapping feature introduced in 2.0, where the given value (assumed to be an immutable scalar value formapped_column.defaultand a simple collection class forrelationship.default_factory) is no longer passed to the@dataclassAPI as a real default, instead a token that leaves the value un-set in the object’s__dict__is used, in conjunction with a descriptor-level default. This prevents an un-set default value from overriding a default that was actually set elsewhere, such as in relationship / foreign key assignment patterns as well as inSession.merge()scenarios. See the full writeup in the What’s New in SQLAlchemy 2.1? document which includes guidance on how to re-enable the 2.0 version of the behavior if needed.References: #12168
[orm] [bug] ¶
The behavior of
with_polymorphic()when used with a single inheritance mapping has been changed such that its behavior should match as closely as possible to that of an equivalent joined inheritance mapping. Specifically this means that the base class specified in thewith_polymorphic()construct will be the basemost class that is loaded, as well as all descendant classes of that basemost class. The change includes that the descendant classes named will no longer be exclusively indicated in “WHERE polymorphic_col IN” criteria; instead, the whole hierarchy starting with the given basemost class will be loaded. If the query indicates that rows should only be instances of a specific subclass within the polymorphic hierarchy, an error is raised if an incompatible superclass is loaded in the result since it cannot be made to match the requested class; this behavior is the same as what joined inheritance has done for many years. The change also allows a single result set to include column-level results from multiple sibling classes at once which was not previously possible with single table inheritance.References: #12395
[orm] [bug] ¶
Improved the behavior of standalone “operators” like
desc(),asc(),all_(), etc. so that they consult the given expression object for an overriding method for that operator, even if the object is not itself aClauseElement, such as if it’s an ORM attribute. This allows custom comparators for things likecomposite()to provide custom implementations of methods likedesc(),asc(), etc.References: #12769
[orm] [bug] ¶
ORM entities can now be involved within the SQL expressions used within
relationship.primaryjoinandrelationship.secondaryjoinparameters without the ORM entity information being implicitly sanitized, allowing ORM-specific features such as single-inheritance criteria in subqueries to continue working even when used in this context. This is made possible by overall ORM simplifications that occurred as of the 2.0 series. The changes here also provide a performance boost (up to 20%) for certain query compilation scenarios.References: #12843
[orm] [bug] ¶
The
SessionEvents.do_orm_execute()event now allows direct mutation or replacement of theORMExecuteState.parametersdictionary or list, which will take effect when the the statement is executed. Previously, changes to this collection were not accommodated by the event hook. Pull request courtesy Shamil.References: #12921
[orm] [bug] ¶
A change in the mechanics of how Python dataclasses are applied to classes that use
MappedAsDataclassorregistry.mapped_as_dataclass()to apply__annotations__that are as identical as is possible to the original__annotations__given, while also adding attributes that SQLAlchemy considers to be part of dataclass__annotations__, then restoring the previous annotations in exactly the same format as they were, using patterns that work with PEP 649 as closely as possible.References: #13021
[orm] [bug] ¶
Removed the
ORDER BYclause from queries generated byselectin_polymorphic()and theMapper.polymorphic_loadparameter set to"selectin". TheORDER BYclause appears to have been an unnecessary implementation artifact.References: #13060
[orm] [bug] ¶
A significant change to the ORM mechanics involved with both
with_loader_criteria()as well as single table inheritance, to more aggressively locate WHERE criteria which should be augmented by either the custom criteria or single-table inheritance criteria; SELECT statements that do not include the entity within the columns clause or as an explicit FROM, but still reference the entity within the WHERE clause, are now covered, in particular this will allow subqueries usingEXISTS (SELECT 1)such as those rendered byComparator.any()andComparator.has().References: #13070
[orm] ¶
The
noload()relationship loader option and relatedlazy='noload'setting is deprecated and will be removed in a future release. This option was originally intended for custom loader patterns that are no longer applicable in modern SQLAlchemy.References: #11045
[orm] ¶
Ignore
Session.join_transaction_modein all cases when the bind provided to theSessionis anEngine. Previously if an event that executed before the session logic, likeConnectionEvents.engine_connect(), left the connection with an active transaction, theSession.join_transaction_modebehavior took place, leading to a surprising behavior.References: #11163
engine¶
[engine] [usecase] ¶
Added new execution option
Connection.execution_options.driver_column_names. This option disables the “name normalize” step that takes place against the DBAPIcursor.descriptionfor uppercase-default backends like Oracle, and will cause the keys of a result set (e.g. named tuple names, dictionary keys inRow._mapping, etc.) to be exactly what was delivered in cursor.description. This is mostly useful for plain textual statements usingtext()orConnection.exec_driver_sql().References: #10789
[engine] [change] ¶
An empty sequence passed to any
execute()method now raised a deprecation warning, since such an executemany is invalid. Pull request courtesy of Carlos Sousa.References: #9647
[engine] [change] ¶
The private method
Connection._execute_compiledis removed. This method may have been used for some special purposes however theSQLCompilerobject has lots of special state that should be set up for an execute call, which we don’t support.[engine] [bug] ¶
Fixed issue in “insertmanyvalues” feature where an INSERT..RETURNING that also made use of a sentinel column to track results would fail to filter out the additional column when
Result.unique()were used to uniquify the result set.References: #10802
[engine] [bug] ¶
Adjusted URL parsing and stringification to apply url quoting to the “database” portion of the URL. This allows a URL where the “database” portion includes special characters such as question marks to be accommodated.
References: #11234
[engine] [bug] ¶
Fixed issue in the
ConnectionEvents.after_cursor_execute()method where the SQL statement and parameter list for an “insertmanyvalues” operation sent to the event would not be the actual SQL / parameters just emitted on the cursor, instead being the non-batched form of the statement that’s used as a template to generate the batched statements.References: #13018
sql¶
[sql] [feature] ¶
Added the ability to create custom SQL constructs that can define new clauses within SELECT, INSERT, UPDATE, and DELETE statements without needing to modify the construction or compilation code of of
Select,Insert,Update, orDeletedirectly. Support for testing these constructs, including caching support, is present along with an example test suite. The use case for these constructs is expected to be third party dialects for analytical SQL (so-called NewSQL) or other novel styles of database that introduce new clauses to these statements. A new example suite is included which illustrates theQUALIFYSQL construct used by several NewSQL databases which includes a cacheable implementation as well as a test suite.References: #12195
[sql] [feature] [core] ¶
The Core operator system now includes the
matmuloperator, i.e. the@operator in Python as an optional operator. In addition to the__matmul__and__rmatmul__operator support this change also adds the missing__rrshift__and__rlshift__. Pull request courtesy Aramís Segovia.References: #12479
[sql] [feature] ¶
Added new Core feature
from_dml_column()that may be used in expressions inside ofUpdateBase.values()for INSERT or UPDATE; this construct will copy whatever SQL expression is used for the given target column in the statement to be used with additional columns. The construct is mostly intended to be a helper with ORMhybrid_propertywithin DML hooks.References: #12496
[sql] [feature] ¶
Added support for Python 3.14+ template strings (t-strings) via the new
tstring()construct. This feature makes use of Python 3.14 template strings as defined in PEP 750, allowing for ergonomic SQL statement construction by automatically interpolating Python values and SQLAlchemy expressions within template strings.References: #12548
[sql] [usecase] ¶
Added new generalized aggregate function ordering to functions via the
aggregate_order_by()method, which receives an expression and generates the appropriate embedded “ORDER BY” or “WITHIN GROUP (ORDER BY)” phrase depending on backend database. This new function supersedes the use of the PostgreSQLaggregate_order_by()function, which remains present for backward compatibility. To complement the new parameter, theaggregate_strings.order_bywhich adds ORDER BY capability to theaggregate_stringsdialect-agnostic function which works for all included backends. Thanks much to Reuven Starodubski with help on this patch.References: #12853
[sql] [usecase] ¶
Changed the query style for ORM queries emitted by
Session.get()as well as many-to-one lazy load queries to use the default labeling style,SelectLabelStyle.LABEL_STYLE_DISAMBIGUATE_ONLY, which normally does not apply labels to columns in a SELECT statement. Previously, the older styleSelectLabelStyle.LABEL_STYLE_TABLENAME_PLUS_COLthat labels columns as <tablename>_<columname> was used forSession.get()to maintain compatibility withQuery. The change allows the string representation of ORM queries to be less verbose in all cases outside of legacyQueryuse. Pull request courtesy Inada Naoki.References: #12932
[sql] [usecase] ¶
Added method
TableClause.insert_column()to complementTableClause.append_column(), which inserts the given column at a specific index. This can be helpful for prepending primary key columns to tables, etc.References: #7910
[sql] [usecase] ¶
Added support for the pow operator (
**), with a default SQL implementation of thePOW()function. On Oracle Database, PostgreSQL and MSSQL it renders asPOWER(). As part of this change, the operator routes through a new first classfuncmemberpow, which renders on Oracle Database, PostgreSQL and MSSQL asPOWER().References: #8579
[sql] [usecase] [orm] ¶
The
Select.filter_by(),Update.filter_by()andDelete.filter_by()methods now search across all entities present in the statement, rather than limiting their search to only the last joined entity or the first FROM entity. This allows these methods to locate attributes unambiguously across multiple joined tables, resolving issues where changing the order of operations such asSelect.with_only_columns()would cause the method to fail.If an attribute name exists in more than one FROM clause entity, an
AmbiguousColumnErroris now raised, indicating thatSelect.filter()(orSelect.where()) should be used instead with explicit table-qualified column references.See also
filter_by() now searches across all FROM clause entities - Migration notes
References: #8601
[sql] [change] ¶
The
.cand.columnsattributes on theSelectandTextualSelectconstructs, which are not instances ofFromClause, have been removed completely, in addition to the.select()method as well as other codepaths which would implicitly generate a subquery from aSelectwithout the need to explicitly call theSelect.subquery()method.In the case of
.cand.columns, these attributes were never useful in practice and have caused a great deal of confusion, hence were deprecated back in version 1.4, and have emitted warnings since that version. Accessing the columns that are specific to aSelectconstruct is done via theSelect.selected_columnsattribute, which was added in version 1.4 to suit the use case that users often expected.cto accomplish. In the larger sense, implicit production of subqueries works against SQLAlchemy’s modern practice of making SQL structure as explicit as possible.Note that this is not related to the usual
FromClause.candFromClause.columnsattributes, common to objects such asTableandSubquery, which are unaffected by this change.See also
A SELECT statement is no longer implicitly considered to be a FROM clause - original notes from SQLAlchemy 1.4
References: #10236
[sql] [change] ¶
the
NumericandFloatSQL types have been separated out so thatFloatno longer inherits fromNumeric; instead, they both extend from a common mixinNumericCommon. This corrects for some architectural shortcomings where numeric and float types are typically separate, and establishes more consistency withIntegeralso being a distinct type. The change should not have any end-user implications except for code that may be usingisinstance()to test for theNumericdatatype; third party dialects which rely upon specific implementation types for numeric and/or float may also require adjustment to maintain compatibility.References: #5252
[sql] [change] ¶
Added new implementation for the
Select.params()method and that of similar statements, via a new statement-onlyExecutableStatement.params()method which works more efficiently and correctly than the previous implementations available fromClauseElement, by associating the given parameter dictionary with the statement overall rather than cloning the statement and rewriting its bound parameters. TheClauseElement.params()andClauseElement.unique_params()methods, when called on an object that does not implementExecutableStatement, will continue to work the old way of cloning the object, and will emit a deprecation warning. This issue both resolves the architectural / performance concerns of #7066 and also provides correct ORM compatibility for functions likealiased(), reported by #12915.[sql] [bug] ¶
The
Doubletype is now used when a Python float value is detected as a literal value to be sent as a bound parameter, rather than theFloattype.Doublehas the same implementation asFloat, but when rendered in a CAST, producesDOUBLEorDOUBLE PRECISIONrather thanFLOAT. The former better matches Python’sfloatdatatype which uses 8-byte double-precision storage. Third party dialects which don’t support theDoubletype directly may need adjustment so that they render an appropriate keyword (e.g.FLOAT) when theDoubledatatype is encountered.References: #10300
[sql] [bug] ¶
Fixed issue in name normalization (e.g. “uppercase” backends like Oracle) where using a
TextualSelectwould not properly maintain as uppercase column names that were quoted as uppercase, even though theTextualSelectincludes aColumnthat explicitly holds this uppercase name.References: #10788
[sql] [bug] ¶
Enhanced the caching structure of the
over.rowsandover.rangeso that different numerical values for the rows / range fields are cached on the same cache key, to the extent that the underlying SQL does not actually change (i.e. “unbounded”, “current row”, negative/positive status will still change the cache key). This prevents the use of many different numerical range/rows value for a query that is otherwise identical from filling up the SQL cache.Note that the semi-private compiler method
_format_frame_clause()is removed by this fix, replaced with a new methodvisit_frame_clause(). Third party dialects which may have referred to this method will need to change the name and revise the approach to rendering the correct SQL for that dialect.References: #11515
[sql] [bug] ¶
Updated the
over()clause to allow non integer values inover.range_clause. Previously, only integer values were allowed and any other values would lead to a failure. To specify a non-integer value, use the newFrameClauseconstruct along with the newFrameClauseTypeenum to specify the frame boundaries. For example:from sqlalchemy import FrameClause, FrameClauseType select( func.sum(table.c.value).over( range_=FrameClause( 3.14, 2.71, FrameClauseType.PRECEDING, FrameClauseType.FOLLOWING, ) ) )
See also
Non-integer RANGE window frame clauses now supported - in the migration guide
References: #12596
[sql] [bug] ¶
Added a new concept of “operator classes” to the SQL operators supported by SQLAlchemy, represented within the enum
OperatorClass. The purpose of this structure is to provide an extra layer of validation when a particular kind of SQL operation is used with a particular datatype, to catch early the use of an operator that does not have any relevance to the datatype in use; a simple example is an integer or numeric column used with a “string match” operator.References: #12736
[sql] [bug] ¶
Fixed an issue in
Select.join_from()where the join condition between the left and right tables specified in the method call could be incorrectly determined based on an intermediate table already present in the FROM clause, rather than matching the foreign keys between the immediate left and right arguments. The join condition is now determined by matching primary keys between the two tables explicitly passed toSelect.join_from(), ensuring consistent and predictable join behavior regardless of the order of join operations or other tables present in the query. The fix is applied to both the Core and ORM implementations ofSelect.join_from().References: #12931
[sql] [bug] ¶
Fixed issue where anonymous label generation for
CTEconstructs could produce name collisions when Python’s garbage collector reused memory addresses during complex query compilation. The anonymous name generation forCTEand other aliased constructs likeAlias,Subqueryand others now useos.urandom()to generate unique identifiers instead of relying on objectid(), ensuring uniqueness even in cases of aggressive garbage collection and memory reuse.References: #12990
[sql] ¶
Removed the automatic coercion of executable objects, such as
Query, when passed intoSession.execute(). This usage raised a deprecation warning since the 1.4 series.References: #12218
schema¶
[schema] [feature] ¶
Added support for the SQL
CREATE VIEWstatement via the newCreateViewDDL class. The new class allows creating database views from SELECT statements, with support for options such asTEMPORARY,IF NOT EXISTS, andMATERIALIZEDwhere supported by the target database. Views defined withCreateViewintegrate withMetaDatafor automated DDL generation and provide aTableobject for querying.References: #181
[schema] [feature] ¶
Added support for the SQL
CREATE TABLE ... AS SELECTconstruct via the newCreateTableAsDDL construct and theSelect.into()method. The new construct allows creating a table directly from the results of a SELECT statement, with support for options such asTEMPORARYandIF NOT EXISTSwhere supported by the target database. Tables defined withCreateTableAsintegrate withMetaDatafor automated DDL generation and provide aTableobject for querying. Pull request courtesy Greg Jarzab.References: #4950
[schema] [usecase] ¶
The the parameter
DropConstraint.isolate_from_tablewas deprecated since it has no effect on the drop table behavior. Its default values was also changed toFalse.References: #13006
[schema] [bug] ¶
The
FloatandNumerictypes are no longer automatically considered as auto-incrementing columns when theColumn.autoincrementparameter is left at its default of"auto"on aColumnthat is part of the primary key. When the parameter is set toTrue, aNumerictype will be accepted as an auto-incrementing datatype for primary key columns, but only if its scale is explicitly given as zero; otherwise, an error is raised. This is a change from 2.0 where all numeric types including floats were automatically considered as “autoincrement” for primary key columns.References: #11811
[schema] ¶
Deprecate Oracle only parameters
Sequence.order,Identity.orderandIdentity.on_null. They should be configured using the dialect kwargsoracle_orderandoracle_on_null.References: #10247
typing¶
[typing] [feature] ¶
The
Rowobject now no longer makes use of an intermediaryTuplein order to represent its individual element types; instead, the individual element types are present directly, via new PEP 646 integration, now available in more recent versions of Mypy. Mypy 1.7 or greater is now required for statements, results and rows to be correctly typed. Pull request courtesy Yurii Karabas.References: #10635
[typing] [bug] ¶
Fixed typing issues where ORM mapped classes and aliased entities could not be used as keys in result row mappings or as join targets in select statements. Patterns such as
row._mapping[User],row._mapping[aliased(User)],row._mapping[with_polymorphic(...)](rejected by both mypy and Pylance), and.join(aliased(User))(rejected by Pylance) are documented and fully supported at runtime but were previously rejected by type checkers. The type definitions for_KeyTypeand_FromClauseArgumenthave been updated to accept these ORM entity types.This change is also backported to: 2.0.46
References: #13075
[typing] ¶
The default implementation of
TypeEngine.python_typenow returnsobjectinstead ofNotImplementedError, since that’s the base for all types in Python3. Thepython_typeofJSONno longer returnsdict, but instead fallbacks to the generic implementation.References: #10646
[typing] [orm] ¶
Removed the deprecated mypy plugin. The plugin was non-functional with newer version of mypy and it’s no longer needed with modern SQLAlchemy declarative style.
References: #12293
[typing] [orm] ¶
Deprecated the
declarative_mixindecorator since it was used only by the now removed mypy plugin.References: #12346
asyncio¶
[asyncio] [feature] ¶
The “emulated” exception hierarchies for the asyncio drivers such as asyncpg, aiomysql, aioodbc, etc. have been standardized on a common base
EmulatedDBAPIException, which is now what’s available from theStatementException.origattribute on a SQLAlchemyDBAPIErrorobject. WithinEmulatedDBAPIExceptionand the subclasses in its hierarchy, the original driver-level exception is also now available via theEmulatedDBAPIException.origattribute, and is also available fromDBAPIErrordirectly using theDBAPIError.driver_exceptionattribute.References: #8047
[asyncio] [change] ¶
Added an initialize step to the import of
sqlalchemy.ext.asyncioso thatgreenletwill be imported only when the asyncio extension is first imported. Alternatively, thegreenletlibrary is still imported lazily on first use to support use case that don’t make direct use of the SQLAlchemy asyncio extension.References: #10296
[asyncio] [change] ¶
Adapted all asyncio dialects, including aiosqlite, aiomysql, asyncmy, psycopg, asyncpg to use the generic asyncio connection adapter first added in #6521 for the aioodbc DBAPI, allowing these dialects to take advantage of a common framework.
References: #10415
[asyncio] [change] ¶
Removed the compatibility
async_fallbackmode for async dialects, since it’s no longer used by SQLAlchemy tests. Also removed the internal functionawait_fallback()and renamed the internal functionawait_only()toawait_(). No change is expected to user code.[asyncio] [bug] ¶
Refactored all asyncio dialects so that exceptions which occur on failed connection attempts are appropriately wrapped with SQLAlchemy exception objects, allowing for consistent error handling.
References: #11956
postgresql¶
[postgresql] [feature] ¶
Adds a new
strsubclassBitStringrepresenting PostgreSQL bitstrings in python, that includes functionality for converting to and fromintandbytes, in addition to implementing utility methods and operators for dealing with bits.This new class is returned automatically by the
postgresql.BITtype.References: #10556
[postgresql] [feature] ¶
Support for storage parameters in
CREATE TABLEusing theWITHclause has been added. Thepostgresql_withdialect option ofTableaccepts a mapping of key/value options.See also
WITH - in the PostgreSQL dialect documentation
References: #10909
[postgresql] [feature] ¶
Added syntax extension
distinct_on()to buildDISTINCT ONclauses. The old api, that passed columns toSelect.distinct(), is now deprecated.References: #12342
[postgresql] [feature] ¶
Support for
VIRTUALcomputed columns on PostgreSQL 18 and later has been added. The default behavior whenComputed.persistedis not specified has been changed to align with PostgreSQL 18’s default ofVIRTUAL. WhenComputed.persistedis not specified, no keyword is rendered on PostgreSQL 18 and later; on older versions a warning is emitted andSTOREDis used as the default. To explicitly requestSTOREDbehavior on all PostgreSQL versions, specifypersisted=True.References: #12866
[postgresql] [feature] [sql] ¶
Added support for monotonic server-side functions such as PostgreSQL 18’s
uuidv7()to work with the “Insert Many Values” Behavior for INSERT statements feature. By passingmonotonic=Trueto anyFunction, the function can be used as a sentinel for tracking row order in batched INSERT operations with RETURNING, allowing the ORM and Core to efficiently batch INSERT statements while maintaining deterministic row ordering.See also
Support for Server-Side Monotonic Functions such as uuidv7() in Batched INSERT Operations
Configuring Monotonic Functions such as UUIDV7
PostgreSQL 18 and above UUID with uuidv7 as a server default
References: #13014
[postgresql] [feature] ¶
Added additional emulated error classes for the subclasses of
asyncpg.exception.IntegrityErrorincludingRestrictViolationError,NotNullViolationError,ForeignKeyViolationError,UniqueViolationErrorCheckViolationError,ExclusionViolationError. These exceptions are not directly thrown by SQLAlchemy’s asyncio emulation, however are available from the newly addedDBAPIError.driver_exceptionattribute when aIntegrityErroris caught.References: #8047
[postgresql] [usecase] ¶
Added new parameter
Enum.create_typeto the CoreEnumclass. This parameter is automatically passed to the correspondingENUMnative type during DDL operations, allowing control over whether the PostgreSQL ENUM type is implicitly created or dropped within DDL operations that are otherwise targeting tables only. This provides control over theENUM.create_typebehavior without requiring explicit creation of aENUMobject.References: #10604
[postgresql] [usecase] ¶
The PostgreSQL dialect now support reflection of table options, including the storage parameters, table access method and table spaces. These options are automatically reflected when autoloading a table, and are also available via the
Inspector.get_table_options()andInspector.get_multi_table_optionsmethod()methods.References: #10909
[postgresql] [usecase] ¶
Added support for PostgreSQL 14+ HSTORE subscripting syntax. When connected to PostgreSQL 14 or later, HSTORE columns now automatically use the native subscript notation
hstore_col['key']instead of the arrow operatorhstore_col -> 'key'for both read and write operations. This provides better compatibility with PostgreSQL’s native HSTORE subscripting feature while maintaining backward compatibility with older PostgreSQL versions.Warning
Indexes in existing PostgreSQL databases which were indexed on an HSTORE subscript expression would need to be updated in order to match the new SQL syntax.
See also
HSTORE subscripting now uses native PostgreSQL 14+ syntax - in the migration guide
References: #12948
[postgresql] [usecase] ¶
The default DBAPI driver for the PostgreSQL dialect has been changed to
psycopg(psycopg version 3) instead ofpsycopg2. Thepsycopg2driver remains fully supported and can be explicitly specified in the connection URL usingpostgresql+psycopg2://.The
psycopg(version 3) driver includes improvements overpsycopg2including better performance when using C extensions and native support for async operations.References: #13010
[postgresql] [change] ¶
The
Comparator.any()andComparator.all()methods for theARRAYtype are now deprecated for removal; these two methods along withAny()andAll()have been legacy for some time as they are superseded by theany_()andall_()functions, which feature more intuitive use.References: #10821
[postgresql] [change] ¶
Named types such as
ENUMandDOMAIN(as well as the dialect-agnosticEnumversion) are now more strongly associated with theMetaDataat the top of the table hierarchy and are de-associated with any particularTablethey may be a part of. This better represents how PostgreSQL named types exist independently of any particular table, and that they may be used across many tables simultaneously. The change impacts the behavior of the “default schema” for a named type, as well as the CREATE/DROP behavior in relationship to theMetaDataandTableconstruct. The change also includes a newCheckFirstenumeration which allows fine grained control over “check” queries during DDL operations, as well as that theSchemaType.inherit_schemaparameter is deprecated and will emit a deprecation warning when used. See the migration notes for full details.See also
Changes to Named Type Handling in PostgreSQL - Complete details on PostgreSQL named type changes
[postgresql] [bug] ¶
Fixed issue where PostgreSQL JSONB operators
Comparator.path_match()andComparator.path_exists()were applying incorrectVARCHARcasts to the right-hand side operand when used with newer PostgreSQL drivers such as psycopg. The operators now indicate the right-hand type asJSONPATH, which currently results in no casting taking place, but is also compatible with explicit casts if the implementation were require it at a later point.This change is also backported to: 2.0.46
References: #13059
[postgresql] [bug] ¶
Fixed regression in PostgreSQL dialect where JSONB subscription syntax would generate incorrect SQL for
cast()expressions returning JSONB, causing syntax errors. The dialect now properly wraps cast expressions in parentheses when using the[]subscription syntax, generating(CAST(...))[index]instead ofCAST(...)[index]to comply with PostgreSQL syntax requirements. This extends the fix from #12778 which addressed the same issue for function calls.This change is also backported to: 2.0.46
References: #13067
[postgresql] [bug] ¶
Improved the foreign key reflection regular expression pattern used by the PostgreSQL dialect to be more permissive in matching identifier characters, allowing it to correctly handle unicode characters in table and column names. This change improves compatibility with PostgreSQL variants such as CockroachDB that may use different quoting patterns in combination with unicode characters in their identifiers. Pull request courtesy Gord Thompson.
This change is also backported to: 2.0.46
[postgresql] [bug] ¶
A
CompileErroris raised if attempting to create a PostgreSQLENUMorDOMAINdatatype using a name that matches a known pg_catalog datatype name, and a default schema is not specified. These types must be explicit within a schema in order to be differentiated from the built-in pg_catalog type. The “public” or otherwise default schema is not chosen by default here since the type can only be reflected back using the explicit schema name as well (it is otherwise not visible due to the pg_catalog name). Pull request courtesy Kapil Dagur.References: #12761
mysql¶
[mysql] [feature] ¶
Added new construct
limit()which can be applied to anyupdate()ordelete()to provide the LIMIT keyword to UPDATE and DELETE. This new construct supersedes the use of the “mysql_limit” dialect keyword argument.[mysql] [mariadb] [reflection] ¶
Updated the reflection logic for indexes in the MariaDB and MySQL dialect to avoid setting the undocumented
typekey in theReflectedIndexdicts returned byget_indexesmethod.References: #12240
mariadb¶
[mariadb] [usecase] ¶
Modified the MariaDB dialect so that when using the
Uuiddatatype with MariaDB >= 10.7, leaving theUuid.native_uuidparameter at its default of True, the nativeUUIDdatatype will be rendered in DDL and used for database communication, rather thanCHAR(32)(the non-native UUID type) as was the case previously. This is a behavioral change since 2.0, where the genericUuiddatatype deliveredCHAR(32)for all MySQL and MariaDB variants. Support for all major DBAPIs is implemented including support for less common “insertmanyvalues” scenarios where UUID values are generated in different ways for primary keys. Thanks much to Volodymyr Kochetkov for delivering the PR.References: #10339
[mariadb] [bug] ¶
Fixed the SQL compilation for the mariadb sequence “NOCYCLE” keyword that is to be emitted when the
Sequence.cycleparameter is set to False on aSequence. Pull request courtesy Diego Dupin.This change is also backported to: 2.0.46
References: #13070
[mariadb] [bug] ¶
Fixes to the MySQL/MariaDB dialect so that mariadb-specific features such as the
INET4andINET6datatype may be used with anEnginethat uses amysql://URL, if the backend database is actually a mariadb database. Previously, support for MariaDB features whenmysql://URLs were used instead ofmariadb://URLs was ad-hoc; with this issue resolution, the full set of schema / compiler / type features are now available regardless of how the URL was presented.References: #13076
sqlite¶
[sqlite] [bug] ¶
Fixed issue in the aiosqlite driver where SQLAlchemy’s setting of aiosqlite’s worker thread to “daemon” stopped working because the aiosqlite architecture moved the location of the worker thread in version 0.22.0. This “daemon” flag is necessary so that a program is able to exit if the SQLite connection itself was not explicitly closed, which is particularly likely with SQLAlchemy as it maintains SQLite connections in a connection pool. While it’s perfectly fine to call
AsyncEngine.dispose()before program exit, this is not historically or technically necessary for any driver of any known backend, since a primary feature of relational databases is durability. The change also implements support for “terminate” with aiosqlite when using version version 0.22.1 or greater, which implements a sync.stop()method.This change is also backported to: 2.0.46
References: #13039
[sqlite] [bug] ¶
Improved the behavior of JSON accessors
Comparator.as_string(),Comparator.as_boolean(),Comparator.as_float(),Comparator.as_integer()to use CAST in a similar way that the PostgreSQL, MySQL and SQL Server dialects do to help enforce the expected Python type is returned.References: #11074
mssql¶
[mssql] [usecase] ¶
Added support for the
IF EXISTSclause when dropping indexes on SQL Server 2016 (13.x) and later versions. TheDropIndex.if_existsparameter is now honored by the SQL Server dialect, allowing conditional index drops that will not raise an error if the index does not exist. Pull request courtesy Edgar Ramírez Mondragón.This change is also backported to: 2.0.46
References: #13045
[mssql] [bug] ¶
The
Comparator.as_boolean()method when used on a JSON value on SQL Server will now force a cast to occur for values that are not simple true/false JSON literals, forcing SQL Server to attempt to interpret the given value as a 1/0 BIT, or raise an error if not possible. Previously the expression would return NULL.References: #11074
[mssql] [bug] ¶
Fix mssql+pyodbc issue where valid plus signs in an already-unquoted
odbc_connect=(raw DBAPI) connection string are replaced with spaces.The pyodbc connector would unconditionally pass the odbc_connect value to unquote_plus(), even if it was not required. So, if the (unquoted) odbc_connect value contained
PWD=pass+wordthat would get changed toPWD=pass word, and the login would fail. One workaround was to quote just the plus sign —PWD=pass%2Bword— which would then get unquoted toPWD=pass+word.References: #11250
oracle¶
[oracle] [feature] ¶
Added support for native BOOLEAN support in Oracle Database 23c and above. The Oracle dialect now renders
BOOLEANautomatically whenBooleanis used in DDL, and also now supports direct use of theBOOLEANdatatype, when 23c and above is in use. For Oracle versions prior to 23c, boolean values continue to be emulated using SMALLINT as before. Special case handling is also present to ensure a SMALLINT that’s interpreted with theBooleandatatype on Oracle Database 23c and above continues to return bool values. Pull request courtesy Yeongbae Jeon.See also
References: #11633
[oracle] [usecase] ¶
The default DBAPI driver for the Oracle Database dialect has been changed to
oracledbinstead ofcx_oracle. Thecx_oracledriver remains fully supported and can be explicitly specified in the connection URL usingoracle+cx_oracle://.The
oracledbdriver is a modernized version ofcx_oraclewith better performance characteristics and ongoing active development from Oracle.References: #13010
tests¶
[tests] [change] ¶
The top-level test runner has been changed to use
nox, adding anoxfile.pyas well as some included modules. Thetox.inifile remains in place so thattoxruns will continue to function in the near term, however it will be eventually removed and improvements and maintenance going forward will be only towardsnoxfile.py.
misc¶
[misc] [changed] ¶
Removed multiple api that were deprecated in the 1.3 series and earlier. The list of removed features includes:
The
forceparameter ofIdentifierPreparer.quoteandIdentifierPreparer.quote_schema;The
threadedparameter of the cx-Oracle dialect;The
_json_serializerand_json_deserializerparameters of the SQLite dialect;The
collection.converterdecorator;The
Mapper.mapped_tableproperty;The
Session.close_allmethod;
References: #12441