Skip to content

2.1.0b1

Pre-release
Pre-release

Choose a tag to compare

@sqla-tester sqla-tester released this 21 Jan 20:56
· 127 commits to main since this release

2.1.0b1

Released: January 21, 2026

platform

  • [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 greenlet dependency 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 the sqlalchemy[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.

    References: #10357, #12029, #12819

orm

  • [orm] [feature] The _orm.relationship.back_populates argument to
    _orm.relationship() 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 by
    _orm.relationship.back_populates. This change allows type
    checkers and IDEs to confirm the argument for
    _orm.relationship.back_populates is 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 as hybrid_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 new _sql.from_dml_column()
    construct.

    References: #12496

  • [orm] [feature] Added new parameter _orm.composite.return_none_on to
    _orm.composite(), which allows control over if and when this
    composite attribute should resolve to None when 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. When _orm.composite.return_none_on is
    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 as Mapped[SomeClass|None], a
    _orm.composite.return_none_on rule is applied that will return
    None if all contained columns are themselves None.

    References: #12570

  • [orm] [feature] Added support for per-session execution options that are merged into all
    queries executed within that session. The _orm.Session,
    _orm.sessionmaker, _orm.scoped_session,
    _ext.asyncio.AsyncSession, and
    _ext.asyncio.async_sessionmaker constructors now accept an
    _orm.Session.execution_options parameter that will be applied
    to all explicit query executions (e.g. using _orm.Session.execute(),
    _orm.Session.get(), _orm.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 _orm.RegistryEvents event class that allows event listeners
    to be established on a _orm.registry object. The new class
    provides three events: _orm.RegistryEvents.resolve_type_annotation()
    which allows customization of type annotation resolution that can
    supplement or replace the use of the
    registry.type_annotation_map dictionary, including that it can
    be helpful with custom resolution for complex types such as those of
    PEP 695, as well as _orm.RegistryEvents.before_configured() and
    _orm.RegistryEvents.after_configured(), which are registry-local
    forms of the mapper-wide version of these hooks.

    References: #9832

  • [orm] [usecase] The _orm.Session.flush.objects parameter is now
    deprecated.

    References: #10816

  • [orm] [usecase] Added the utility method _orm.Session.merge_all() and
    _orm.Session.delete_all() that operate on a collection
    of instances.

    References: #11776

  • [orm] [usecase] Added support for using _orm.with_expression() to populate a
    _orm.query_expression() attribute that is also configured as the
    polymorphic_on discriminator column. The ORM now detects when a query
    expression column is serving as the polymorphic discriminator and updates
    it to use the column provided via _orm.with_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() to _orm.composite() attributes,
    by default applying the modifier to all contained columns. Can be
    overridden using a custom comparator.

    References: #12769

  • [orm] [usecase] The _orm.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 the _orm.aliased() 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. When
    _orm.aliased.adapt_on_names is True, the warning suggests
    checking the column name; when False, it suggests using the
    adapt_on_names parameter for name-based matching.

    References: #12838

  • [orm] [usecase] Improvements to the use case of using Declarative Dataclass Mapping <orm_declarative_native_dataclasses> with intermediary classes that are
    unmapped. As was the existing behavior, classes can subclass
    _orm.MappedAsDataclass alone without a declarative base to act as
    mixins, or along with a declarative base as well as __abstract__ = True
    to define an abstract base. However, the improved behavior scans ORM
    attributes like _orm.mapped_column() in this case to create correct
    dataclasses.field() constructs based on their arguments, allowing for
    more natural ordering of fields without dataclass errors being thrown.
    Additionally, added a new _orm.unmapped_dataclass() decorator
    function, which may be used to create unmapped mixins in a mapped hierarchy
    that is using the _orm.mapped_dataclass() decorator to create mapped
    dataclasses.

    References: #12854

  • [orm] [usecase] Added _orm.DictBundle as a subclass of _orm.Bundle
    that returns dict objects.

    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_init ORM 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
    _orm.SessionEvents.after_bulk_update() and
    _orm.SessionEvents.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 _orm.relationship() to link to a mapped
    class against an alternative selectable; this use case is now suited by the
    relationship_aliased_class feature.

    References: #12437

  • [orm] [bug] The _orm.relationship.secondary parameter no longer uses Python
    eval() to evaluate the given string. This parameter when passed a
    string should resolve to a table name that's present in the local
    MetaData collection 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 raise TypeError for invalid use of the |,
    &, ^, and - operators, as well as the in-place mutation
    versions of these methods, to match the behavior of standard Python
    set() 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
    _orm.mapped_column.default and
    _orm.relationship.default parameters, as well as the
    _orm.relationship.default_factory parameter with
    collection-based relationships, when used with SQLAlchemy's
    orm_declarative_native_dataclasses feature introduced in 2.0, where
    the given value (assumed to be an immutable scalar value for
    _orm.mapped_column.default and a simple collection class for
    _orm.relationship.default_factory) is no longer passed to the
    @dataclass API 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 in
    _orm.Session.merge() scenarios. See the full writeup in the
    migration_21_toplevel 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 _orm.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 the
    _orm.with_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 _sql.desc(),
    _sql.asc(), _sql.all_(), etc. so that they consult the given
    expression object for an overriding method for that operator, even if the
    object is not itself a ClauseElement, such as if it's an ORM attribute.
    This allows custom comparators for things like _orm.composite() to
    provide custom implementations of methods like desc(), asc(), etc.

    References: #12769

  • [orm] [bug] ORM entities can now be involved within the SQL expressions used within
    _orm.relationship.primaryjoin and
    _orm.relationship.secondaryjoin parameters 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 _events.SessionEvents.do_orm_execute() event now allows direct
    mutation or replacement of the ORMExecuteState.parameters
    dictionary 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 MappedAsDataclass or
    registry.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 BY clause from queries generated by
    _orm.selectin_polymorphic() and the
    _orm.Mapper.polymorphic_load parameter set to "selectin".
    The ORDER BY clause appears to have been an unnecessary implementation
    artifact.

    References: #13060

  • [orm] [bug] A significant change to the ORM mechanics involved with both
    orm.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 using EXISTS (SELECT 1) such as those rendered by RelationshipProperty.Comparator.any()
    and RelationshipProperty.Comparator.has().

    References: #13070

  • [orm] The _orm.noload() relationship loader option and related
    lazy='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 _orm.Session.join_transaction_mode in all cases when
    the bind provided to the _orm.Session is an
    _engine.Engine.
    Previously if an event that executed before the session logic,
    like _engine.ConnectionEvents.engine_connect(),
    left the connection with an active transaction, the
    _orm.Session.join_transaction_mode behavior took
    place, leading to a surprising behavior.

    References: #11163

engine

  • [engine] [usecase] Added new execution option
    _engine.Connection.execution_options.driver_column_names. This
    option disables the "name normalize" step that takes place against the
    DBAPI cursor.description for uppercase-default backends like Oracle,
    and will cause the keys of a result set (e.g. named tuple names, dictionary
    keys in Row._mapping, etc.) to be exactly what was delivered in
    cursor.description. This is mostly useful for plain textual statements
    using _sql.text() or _engine.Connection.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_compiled is removed. This method may
    have been used for some special purposes however the SQLCompiler
    object 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, _sql.Insert, Update, or Delete
    directly. 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 the QUALIFY SQL 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 matmul operator, 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 _sql.from_dml_column() that may be used in
    expressions inside of UpdateBase.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 ORM hybrid_property within
    DML hooks.

    References: #12496

  • [sql] [feature] Added support for Python 3.14+ template strings (t-strings) via the new
    _sql.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
    _functions.FunctionElement.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 PostgreSQL
    _postgresql.aggregate_order_by() function, which remains present for
    backward compatibility. To complement the new parameter, the
    _functions.aggregate_strings.order_by which adds ORDER BY
    capability to the _functions.aggregate_strings dialect-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,
    _sql.SelectLabelStyle.LABEL_STYLE_DISAMBIGUATE_ONLY, which normally
    does not apply labels to columns in a SELECT statement. Previously, the
    older style _sql.SelectLabelStyle.LABEL_STYLE_TABLENAME_PLUS_COL
    that labels columns as _ was used for
    Session.get() to maintain compatibility with _orm.Query.
    The change allows the string representation of ORM queries to be less
    verbose in all cases outside of legacy _orm.Query use. Pull
    request courtesy Inada Naoki.

    References: #12932

  • [sql] [usecase] Added method TableClause.insert_column() to complement
    TableClause.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 the POW() function. On Oracle Database, PostgreSQL
    and MSSQL it renders as POWER(). As part of this change, the operator
    routes through a new first class func member _functions.pow,
    which renders on Oracle Database, PostgreSQL and MSSQL as POWER().

    References: #8579

  • [sql] [usecase] [orm] The _sql.Select.filter_by(), _sql.Update.filter_by() and
    _sql.Delete.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 as
    _sql.Select.with_only_columns() would cause the method to fail.

    If an attribute name exists in more than one FROM clause entity, an
    _exc.AmbiguousColumnError is now raised, indicating that
    _sql.Select.filter() (or _sql.Select.where()) should be used
    instead with explicit table-qualified column references.

    References: #8601

  • [sql] [change] The .c and .columns attributes on the Select and
    TextualSelect constructs, which are not instances of
    FromClause, have been removed completely, in addition to the
    .select() method as well as other codepaths which would implicitly
    generate a subquery from a Select without the need to explicitly
    call the Select.subquery() method.

    In the case of .c and .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 a Select
    construct is done via the Select.selected_columns attribute, which
    was added in version 1.4 to suit the use case that users often expected
    .c to 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.c and
    FromClause.columns attributes, common to objects such as
    Table and Subquery, which are unaffected by this
    change.

    References: #10236

  • [sql] [change] the Numeric and Float SQL types have been separated out
    so that Float no longer inherits from Numeric; instead,
    they both extend from a common mixin NumericCommon. This
    corrects for some architectural shortcomings where numeric and float types
    are typically separate, and establishes more consistency with
    Integer also being a distinct type. The change should not have
    any end-user implications except for code that may be using
    isinstance() to test for the Numeric datatype; 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-only
    ExecutableStatement.params() method which works more efficiently and
    correctly than the previous implementations available from
    ClauseElement, by associating the given parameter dictionary with
    the statement overall rather than cloning the statement and rewriting its
    bound parameters. The _sql.ClauseElement.params() and
    _sql.ClauseElement.unique_params() methods, when called on an object
    that does not implement ExecutableStatement, 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 like _orm.aliased(), reported by #12915.

    References: #12915, #7066

  • [sql] [bug] The Double type is now used when a Python float value is detected
    as a literal value to be sent as a bound parameter, rather than the
    Float type. Double has the same implementation as
    Float, but when rendered in a CAST, produces DOUBLE or
    DOUBLE PRECISION rather than FLOAT. The former better matches
    Python's float datatype which uses 8-byte double-precision storage.
    Third party dialects which don't support the Double type directly
    may need adjustment so that they render an appropriate keyword (e.g.
    FLOAT) when the Double datatype is encountered.

    References: #10300

  • [sql] [bug] Fixed issue in name normalization (e.g. "uppercase" backends like Oracle)
    where using a TextualSelect would not properly maintain as
    uppercase column names that were quoted as uppercase, even though
    the TextualSelect includes a Column that explicitly
    holds this uppercase name.

    References: #10788

  • [sql] [bug] Enhanced the caching structure of the _expression.over.rows
    and _expression.over.range so 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 method
    visit_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 _sql.over() clause to allow non integer values in
    _sql.over.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 new _sql.FrameClause
    construct along with the new _sql.FrameClauseType enum 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,
    )
    )
    )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 _sql.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 to
    _sql.Select.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
    of _sql.Select.join_from().

    References: #12931

  • [sql] [bug] Fixed issue where anonymous label generation for CTE constructs
    could produce name collisions when Python's garbage collector reused memory
    addresses during complex query compilation. The anonymous name generation
    for CTE and other aliased constructs like Alias,
    Subquery and others now use os.urandom() to generate unique
    identifiers instead of relying on object id(), ensuring uniqueness even
    in cases of aggressive garbage collection and memory reuse.

    References: #12990

  • [sql] Removed the automatic coercion of executable objects, such as
    _orm.Query, when passed into _orm.Session.execute().
    This usage raised a deprecation warning since the 1.4 series.

    References: #12218

schema

  • [schema] [feature] Added support for the SQL CREATE VIEW statement via the new
    CreateView DDL class. The new class allows creating database
    views from SELECT statements, with support for options such as
    TEMPORARY, IF NOT EXISTS, and MATERIALIZED where supported by
    the target database. Views defined with CreateView integrate with
    MetaData for automated DDL generation and provide a
    Table object for querying.

    References: #181

  • [schema] [feature] Added support for the SQL CREATE TABLE ... AS SELECT construct via the
    new _schema.CreateTableAs DDL construct and the
    _sql.Select.into() method. The new construct allows creating a
    table directly from the results of a SELECT statement, with support for
    options such as TEMPORARY and IF NOT EXISTS where supported by the
    target database. Tables defined with _schema.CreateTableAs
    integrate with MetaData for automated DDL generation and provide
    a Table object for querying. Pull request courtesy Greg Jarzab.

    References: #4950

  • [schema] [usecase] The the parameter _schema.DropConstraint.isolate_from_table
    was deprecated since it has no effect on the drop table behavior.
    Its default values was also changed to False.

    References: #13006

  • [schema] [bug] The Float and Numeric types are no longer automatically
    considered as auto-incrementing columns when the
    _schema.Column.autoincrement parameter is left at its default
    of "auto" on a _schema.Column that is part of the primary key.
    When the parameter is set to True, a Numeric type 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 _schema.Sequence.order,
    _schema.Identity.order and _schema.Identity.on_null.
    They should be configured using the dialect kwargs oracle_order and
    oracle_on_null.

    References: #10247

typing

  • [typing] [feature] The Row object now no longer makes use of an intermediary
    Tuple in 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] The default implementation of _sql.TypeEngine.python_type now
    returns object instead of NotImplementedError, since that's the
    base for all types in Python3.
    The python_type of _sql.JSON no longer returns dict,
    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_mixin decorator 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 the StatementException.orig attribute on a
    SQLAlchemy DBAPIError object. Within EmulatedDBAPIException
    and the subclasses in its hierarchy, the original driver-level exception is
    also now available via the EmulatedDBAPIException.orig attribute,
    and is also available from DBAPIError directly using the
    DBAPIError.driver_exception attribute.

    References: #8047

  • [asyncio] [change] Added an initialize step to the import of
    sqlalchemy.ext.asyncio so that greenlet will
    be imported only when the asyncio extension is first imported.
    Alternatively, the greenlet library 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_fallback mode for async dialects,
    since it's no longer used by SQLAlchemy tests.
    Also removed the internal function await_fallback() and renamed
    the internal function await_only() to await_().
    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 str subclass _postgresql.BitString representing
    PostgreSQL bitstrings in python, that includes
    functionality for converting to and from int and bytes, in
    addition to implementing utility methods and operators for dealing with bits.

    This new class is returned automatically by the postgresql.BIT type.

    References: #10556

  • [postgresql] [feature] Support for storage parameters in CREATE TABLE using the WITH
    clause has been added. The postgresql_with dialect option of
    _schema.Table accepts a mapping of key/value options.

    References: #10909

  • [postgresql] [feature] Added syntax extension _postgresql.distinct_on() to build DISTINCT ON clauses. The old api, that passed columns to
    _sql.Select.distinct(), is now deprecated.

    References: #12342

  • [postgresql] [feature] Support for VIRTUAL computed columns on PostgreSQL 18 and later has
    been added. The default behavior when Computed.persisted is
    not specified has been changed to align with PostgreSQL 18's default of
    VIRTUAL. When Computed.persisted is not specified, no
    keyword is rendered on PostgreSQL 18 and later; on older versions a
    warning is emitted and STORED is used as the default. To explicitly
    request STORED behavior on all PostgreSQL versions, specify
    persisted=True.

    References: #12866

  • [postgresql] [feature] [sql] Added support for monotonic server-side functions such as PostgreSQL 18's
    uuidv7() to work with the engine_insertmanyvalues feature.
    By passing monotonic=True to any Function, 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.

    References: #13014

  • [postgresql] [feature] Added additional emulated error classes for the subclasses of
    asyncpg.exception.IntegrityError including RestrictViolationError,
    NotNullViolationError, ForeignKeyViolationError,
    UniqueViolationError CheckViolationError,
    ExclusionViolationError. These exceptions are not directly thrown by
    SQLAlchemy's asyncio emulation, however are available from the
    newly added DBAPIError.driver_exception attribute when a
    IntegrityError is caught.

    References: #8047

  • [postgresql] [usecase] Added new parameter Enum.create_type to the Core
    Enum class. This parameter is automatically passed to the
    corresponding _postgresql.ENUM native 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 the
    _postgresql.ENUM.create_type behavior without requiring
    explicit creation of a _postgresql.ENUM object.

    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 _engine.Inspector.get_table_options() and
    _engine.Inspector.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 operator
    hstore_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.

    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.

    References: #12948

  • [postgresql] [usecase] The default DBAPI driver for the PostgreSQL dialect has been changed to
    psycopg (psycopg version 3) instead of psycopg2. The psycopg2
    driver remains fully supported and can be explicitly specified in the
    connection URL using postgresql+psycopg2://.

    The psycopg (version 3) driver includes improvements over psycopg2
    including better performance when using C extensions and native support
    for async operations.

    References: #13010

  • [postgresql] [change] The _types.ARRAY.Comparator.any() and
    _types.ARRAY.Comparator.all() methods for the _types.ARRAY
    type are now deprecated for removal; these two methods along with
    _postgresql.Any() and _postgresql.All() have been legacy for
    some time as they are superseded by the _sql.any_() and
    _sql.all_() functions, which feature more intuitive use.

    References: #10821

  • [postgresql] [change] Named types such as _postgresql.ENUM and
    _postgresql.DOMAIN (as well as the dialect-agnostic
    _types.Enum version) are now more strongly associated with the
    _schema.MetaData at the top of the table hierarchy and are
    de-associated with any particular _schema.Table they 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
    the MetaData and Table construct. The change also
    includes a new CheckFirst enumeration which allows fine grained
    control over "check" queries during DDL operations, as well as that the
    _types.SchemaType.inherit_schema parameter is deprecated and
    will emit a deprecation warning when used. See the migration notes for
    full details.

    References: #10594, #12690

  • [postgresql] [bug] A CompileError is raised if attempting to create a PostgreSQL
    _postgresql.ENUM or _postgresql.DOMAIN datatype 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 _mysql.limit() which can be applied to any
    _sql.update() or _sql.delete() 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 type key in the
    _engine.ReflectedIndex dicts returned by
    _engine.Inspector.get_indexes method.

    References: #12240

mariadb

  • [mariadb] [usecase] Modified the MariaDB dialect so that when using the _sqltypes.Uuid
    datatype with MariaDB >= 10.7, leaving the
    _sqltypes.Uuid.native_uuid parameter at its default of True,
    the native UUID datatype will be rendered in DDL and used for database
    communication, rather than CHAR(32) (the non-native UUID type) as was
    the case previously. This is a behavioral change since 2.0, where the
    generic _sqltypes.Uuid datatype delivered CHAR(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] Fixes to the MySQL/MariaDB dialect so that mariadb-specific features such
    as the mariadb.INET4 and mariadb.INET6 datatype may be
    used with an Engine that uses a mysql:// URL, if the backend
    database is actually a mariadb database. Previously, support for MariaDB
    features when mysql:// URLs were used instead of mariadb:// 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] Improved the behavior of JSON accessors JSON.Comparator.as_string(),
    JSON.Comparator.as_boolean(), JSON.Comparator.as_float(),
    JSON.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] [bug] The JSON.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+word that would get changed to
    PWD=pass word, and the login would fail. One workaround was to quote
    just the plus sign — PWD=pass%2Bword — which would then get unquoted
    to PWD=pass+word.

    References: #11250

oracle

  • [oracle] [feature] Added support for native BOOLEAN support in Oracle Database 23c and above.
    The Oracle dialect now renders BOOLEAN automatically when
    Boolean is used in DDL, and also now supports direct use of the
    BOOLEAN datatype, 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 the Boolean datatype on Oracle
    Database 23c and above continues to return bool values. Pull request
    courtesy Yeongbae Jeon.

    References: #11633

  • [oracle] [usecase] The default DBAPI driver for the Oracle Database dialect has been changed
    to oracledb instead of cx_oracle. The cx_oracle driver remains
    fully supported and can be explicitly specified in the connection URL
    using oracle+cx_oracle://.

    The oracledb driver is a modernized version of cx_oracle with
    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 a
    noxfile.py as well as some included modules. The tox.ini file
    remains in place so that tox runs will continue to function in the near
    term, however it will be eventually removed and improvements and
    maintenance going forward will be only towards noxfile.py.

misc

  • [misc] [changed] Removed multiple api that were deprecated in the 1.3 series and earlier.
    The list of removed features includes:

    -   The `force` parameter of `IdentifierPreparer.quote` and
        `IdentifierPreparer.quote_schema`;
    
    -   The `threaded` parameter of the cx-Oracle dialect;
    
    -   The `_json_serializer` and `_json_deserializer` parameters of the
        SQLite dialect;
    
    -   The `collection.converter` decorator;
    
    -   The `Mapper.mapped_table` property;
    
    -   The `Session.close_all` method;
    
    -   Support for multiple arguments in `_orm.defer()` and
        `_orm.undefer()`.
    

    References: #12441