Visitar URL original
Releases Β· sqlalchemy/sqlalchemy Β· GitHub
Skip to content

Releases: sqlalchemy/sqlalchemy

2.1.6

Choose a tag to compare

@sqla-tester sqla-tester released this 21 Oct 16:25

2.1.6

Released: October 21, 2026

orm

  • [orm] [bug] Fixed issue where a Session flush that replaced an expired
    object in the identity map with a newly flushed object of the same
    primary key would emit a SELECT to refresh the expired object, solely in
    order to determine whether to emit an "Identity map already had an
    identity" warning. This could occur when a row loaded within a
    transaction is rolled back and its primary key value is subsequently
    reused, and could lead to an AttributeError if the expired object
    were garbage collected during that load. The check now emits no SQL, and
    an expired object that is replaced in this way no longer causes the
    warning to be emitted.

    This change is also backported to: 2.0.56

    References: #13711

  • [orm] [bug] [regression] Fixed regression caused by #13070 where using more than one
    orm.with_loader_criteria() option for the same entity, where the
    criteria made use of correlated subqueries referring to that entity, would
    fail with a RecursionError when compiling.

    References: #13701

  • [orm] [bug] [regression] Fixed regression caused by the new implementation of .params() for
    statements introduced in #7066, where parameter values passed to
    the .params() method of an ORM statement created using
    Select.from_statement() would not be applied when the statement
    was compiled without the compiled statement cache, such as when the cache
    was disabled or when the statement was stringified directly, leading to
    an error that a value is required for the bound parameter.

    References: #13709

orm extensions

  • [bug] [orm extensions] Fixed an issue in the instrumentation_toplevel extension where
    unrelated classes inheriting from typing.Generic could incorrectly
    raise a TypeError when using different instrumentation implementations.
    Pull request courtesy TomasCaruso10.

    This change is also backported to: 2.0.56

    References: #13680

engine

  • [engine] [usecase] Added make_url.keep_blank_values and
    URL.update_query_string.keep_blank_values parameters, which
    when set to True retain query string parameters with blank values such as
    key= in the URL.query dictionary as empty strings. This allows
    a URL with empty-string query values to round trip through
    URL.render_as_string() and make_url(). The default behavior
    of omitting blank values is unchanged, as dialects may not accept an empty
    string for query parameters that are coerced to other types.

    References: #13697

sql

  • [sql] [bug] Fixed issues in cache key generation involving the DML constructs
    Insert, Update and Delete. The
    Insert.from_select.include_defaults,
    UpdateBase.return_defaults() and
    UpdateBase.return_defaults.supplemental_cols
    parameters/attributes are now considered as part of the cache key.

    This change is also backported to: 2.0.56

    References: #13686, #13687

  • [sql] [bug] Fixed issue where the individual bound parameters generated for an
    "expanding" parameter, such as that of an IN expression, at statement
    execution time could take on the same name as another bound parameter in
    the same statement, causing the value of the other parameter to be
    silently replaced with that of an element of the IN list. Names are now
    disambiguated from one another in this scenario.

    This change is also backported to: 2.0.56

    References: #13695

asyncio

  • [asyncio] [bug] Fixed issue where calling _asyncio.AsyncResult.unique() or
    _asyncio.AsyncResult.columns() on an _asyncio.AsyncResult
    would have no effect on a subsequent call to
    _asyncio.AsyncResult.scalars() or
    _asyncio.AsyncResult.mappings(), as the resulting
    _asyncio.AsyncScalarResult or
    _asyncio.AsyncMappingResult was built from the underlying
    synchronous result, which did not include those modifications. Unique
    filtering and column selection are now carried over in the same way as for
    _engine.Result.scalars() and _engine.Result.mappings(). Among
    other things, this allows _asyncio.AsyncSession.stream() to be used
    with a joined eager load against a collection, followed by
    .unique().scalars(), which would previously raise an error indicating
    that _asyncio.AsyncResult.unique() must be invoked.

    This change is also backported to: 2.0.56

    References: #13688

  • [asyncio] [bug] Fixed issue where the _asyncio.AsyncResult.freeze() method would
    fail to fetch rows from the result, producing a
    _engine.FrozenResult that would raise TypeError when invoked
    for a Core result, or raising AttributeError immediately for an ORM
    result that returns a single entity per row. The method now fully
    fetches all rows from the async cursor, honoring
    _asyncio.AsyncResult.unique() and
    _asyncio.AsyncResult.columns() in the same way as the synchronous
    _engine.Result.freeze() method.

    This change is also backported to: 2.0.56

    References: #13689

  • [asyncio] [bug] Fixed regression caused by #12710 where a cancellation received
    by _asyncio.AsyncConnection.close() was not handled properly,
    leaving the _asyncio.AsyncConnection in an inconsistent state
    that could interfere with connections subsequently checked out from the
    pool. The connection is now fully closed in this case.

    This change is also backported to: 2.0.56

    References: #13699

mysql

  • [mysql] [bug] Fixed issue where backslashes in the values of a mysql.ENUM or
    mysql.SET type were not escaped when rendered in DDL, leading to
    incorrect values or a malformed statement under MySQL's default
    sql_mode. Backslashes are now escaped in the same way as for other
    string literals, unless NO_BACKSLASH_ESCAPES is in effect. Pull
    request courtesy dxbjavid.

    This change is also backported to: 2.0.56

    References: #13466

2.1.5

Choose a tag to compare

@sqla-tester sqla-tester released this 16 Oct 20:52

2.1.5

Released: October 16, 2026

orm

  • [orm] [feature] Added the _orm.Unmapped annotation and the
    _orm.unmapped_column() construct, providing a declarative-compatible
    approach to having "excluded" columns in a Declarative model, that is,
    columns that are part of the _schema.Table but are not mapped,
    replacing the use of _orm.Mapper.exclude_properties for this
    purpose when using a Declarative mapping.

    References: #13650

  • [orm] [bug] [regression] Fixed regression caused by #11917 in version 2.0.36 where an ORM
    enabled _sql.update() using WHERE criteria which matched more than
    one object present in the _orm.Session would expire server-side
    onupdate columns, as well as columns that were SET to a SQL expression, on
    only one of the matched objects, leaving the remaining objects with stale
    values for those attributes. The columns are now expired for all matched
    objects.

    This change is also backported to: 2.0.55

    References: #13675

  • [orm] [deprecated] Using the _orm.Mapper.exclude_properties or
    _orm.Mapper.include_properties parameters on a Declarative
    mapping to exclude attributes that are declared on the class, such as with
    _orm.mapped_column(), is deprecated and now emits a deprecation
    warning. This use was never supported, and depending on the arguments
    passed to _orm.mapped_column(), the attribute could remain mapped
    regardless; in particular, as of 2.1 this includes ORM mapped dataclasses
    where _orm.mapped_column.default is present. The new
    _orm.unmapped_column() construct should be used instead. Excluding
    columns that are present only in the _schema.Table, such as those
    from __table__, __table_args__ or table reflection, is not
    affected.

    References: #13650

schema

  • [schema] [bug] Fixed issue where dialect-specific keyword arguments established on a
    ForeignKeyConstraint, ForeignKey or
    CheckConstraint, such as postgresql_not_valid, were not
    carried along to the copy of the constraint produced by
    Table.to_metadata(), so that the copied Table would
    generate DDL without these options. The fix for #5276 had
    established this behavior for Column,
    PrimaryKeyConstraint and UniqueConstraint only.

    This change is also backported to: 2.0.55

    References: #13677

asyncio

  • [asyncio] [bug] Fixed issue where a task that was cancelled a second time while the
    connection pool was closing an asyncio connection invalidated by the
    first cancellation, such as one received while a statement was
    executing, would leave the closed connection in place in the pool, where
    it would be handed out to the next checkout and fail upon first use.
    The pool now discards the connection in all cases once a close has been
    attempted. This condition occurs unconditionally for tasks cancelled by
    an AnyIO cancel scope, which delivers the cancellation repeatedly until
    the task exits the scope.

    This change is also backported to: 2.0.55

    References: #13673

  • [asyncio] [bug] Fixed regression caused by #13548 where an asyncio cancellation
    delivered while the pool was closing a connection stranded by an
    exception raised within the _events.PoolEvents.connect() event
    handler would be suppressed, with the original exception raised in its
    place. The task would then not end as cancelled, and code that catches
    ordinary exceptions in order to retry would continue running after it
    had been cancelled. The CancelledError is now propagated.

    As part of this change, a variety of other cancellation scenarios were
    caught and repaired.

    This change is also backported to: 2.0.55

    References: #13678

2.0.55

Choose a tag to compare

@sqla-tester sqla-tester released this 16 Oct 20:45

2.0.55

Released: October 16, 2026

orm

  • [orm] [bug] Fixed issue in Annotated Declarative where an annotation naming an
    undefined symbol, where the annotation is not wrapped in
    _orm.Mapped, would raise TypeError rather than the expected
    MappedAnnotationError when running under Python 3.14 without
    from __future__ import annotations in effect. Under PEP 649, such
    an annotation is delivered as a ForwardRef rather than a string, a
    form which was not accommodated by the check that determines whether an
    unresolvable annotation was intended to be mapped.

    References: #13602

  • [orm] [bug] [regression] Fixed regression caused by #11917 in version 2.0.36 where an ORM
    enabled _sql.update() using WHERE criteria which matched more than
    one object present in the _orm.Session would expire server-side
    onupdate columns, as well as columns that were SET to a SQL expression, on
    only one of the matched objects, leaving the remaining objects with stale
    values for those attributes. The columns are now expired for all matched
    objects.

    References: #13675

orm extensions

  • [bug] [orm extensions] Fixed issue in the mutable_toplevel extension where the in-place
    |= operator on MutableDict and the in-place *= operator
    on MutableList did not emit change events, so that the
    modification was not detected and was not persisted on the next flush.
    These operators now invoke Mutable.changed() in the same way as
    the in-place set operators of MutableSet and the +=
    operator of MutableList.

    References: #13643

engine

  • [engine] [bug] [regression] Fixed regression from the 1.4 series where the DBAPI cursor used for a
    compiled statement that returns no rows, which includes DDL constructs
    such as CreateTable and DropTable as well as the
    SAVEPOINT, ROLLBACK TO SAVEPOINT and RELEASE SAVEPOINT statements emitted
    by _engine.Connection.begin_nested(), was not closed after
    execution, and was instead left to be released when garbage collected.
    The cursor is now closed immediately, as was the case in 1.3 and as is
    already the case for INSERT, UPDATE, DELETE and _sql.text()
    statements.

    References: #13640

  • [engine] [bug] Fixed issue in the
    _engine.Connection.execution_options.logging_token option
    where setting the value to None or another false value would raise
    AttributeError if a logging token had not already been established on
    that _engine.Connection, such as on a newly checked out
    connection, when the option was set at the _engine.Engine level,
    or when the token was reset to None more than once. Regression
    introduced in 2.0.30 as part of #11210.

    References: #13641

sql

  • [sql] [performance] Added instance-level caching to the "comparator factory" method of the
    TypeDecorator class. This attribute is used in most SQL
    comparison operations and was generating a new class on the fly each time
    it was called. Special steps have been taken to allow this class to be
    cached while still allowing for pickling of the TypeDecorator
    itself.

    References: #13617

  • [sql] [bug] Fixed an additional race condition related to those fixed as part of
    #12302, where concurrent access to the
    FromClause.primary_key or FromClause.foreign_keys
    collections on a newly created Alias or Subquery
    could raise an AssertionError if another thread was in the midst of
    generating these collections.

    References: #12302

  • [sql] [bug] Fixed issue where literal text within a SQL statement that resembled a
    pyformat bound parameter, such as %(name)s, would be rewritten as a
    bound parameter placeholder when compiling for a dialect using a
    positional paramstyle such as qmark, format, numeric or
    numeric_dollar. This would occur for string values rendered with the
    literal_binds compiler option, as well as for literal text within
    _sql.text() or _sql.literal_column() constructs, leading to a
    SQL statement that silently compared against a different value, or that
    raised a KeyError for numeric paramstyles. This was a regression
    caused by the change in the 2.0 series where statements for positional
    paramstyles are first compiled using named parameters, which are then
    converted to positional placeholders.

    References: #13609

  • [sql] [bug] The rendering of bound parameters within a _sql.text() construct no
    longer considers the $ character to be part of a bound parameter name,
    matching the parsing already performed by _sql.text() itself when
    the construct is created. Previously, a string such as :foo$bar would
    be rendered as a single bound parameter named foo$bar, for which a
    value could not be passed, and which would fail when executed. The
    string is now rendered as the bound parameter foo followed by the
    literal text $bar. Additionally, _sql.text() no longer
    establishes a spurious bound parameter from a name that is immediately
    followed by a colon, such as thi for the string :this:that or
    va for the string :val::int; these strings are not rendered as
    bound parameters, and _sql.text() now uses the same parsing as the
    compiler to determine this.

    References: #13609

  • [sql] [bug] Fixed issue where the
    UpdateBase.return_defaults.supplemental_cols parameter of
    UpdateBase.return_defaults() was not included in the cache key of
    an Insert or Update construct, causing a statement
    that used this parameter to share a cached compiled form with an otherwise
    identical statement that did not, or that used different supplemental
    columns. This would cause the supplemental RETURNING rows to be missing
    or incorrect depending on which form of the statement was compiled first.

schema

  • [schema] [bug] Fixed issue where dialect-specific keyword arguments established on a
    ForeignKeyConstraint, ForeignKey or
    CheckConstraint, such as postgresql_not_valid, were not
    carried along to the copy of the constraint produced by
    Table.to_metadata(), so that the copied Table would
    generate DDL without these options. The fix for #5276 had
    established this behavior for Column,
    PrimaryKeyConstraint and UniqueConstraint only.

    References: #13677

asyncio

  • [asyncio] [bug] Fixed issue where cancelling a task while an asyncio context manager
    such as _asyncio.AsyncConnection or _asyncio.AsyncSession
    was closing on exit would allow the task to complete while the close
    continued in the background. An application that disposed of the
    engine at that point, as is typical at shutdown, would have the
    connection returned to the disposed pool afterwards, where nothing
    would close it. The context manager now waits for the close to complete
    before propagating the cancellation.

    Also fixed related issues in the connection pool where a cancellation
    received while a connection was being reset on return to the pool, or a
    second cancellation received while the pool was cleaning up after a
    first cancellation during checkout, would prevent the connection's slot
    from being returned to the pool, reducing the pool's capacity until the
    connection object was garbage collected, or permanently in the latter
    case.

    References: #12710, #13622

  • [asyncio] [bug] Fixed issue where a task that was cancelled a second time while the
    connection pool was closing an asyncio connection invalidated by the
    first cancellation, such as one received while a statement was
    executing, would leave the closed connection in place in the pool, where
    it would be handed out to the next checkout and fail upon first use.
    The pool now discards the connection in all cases once a close has been
    attempted. This condition occurs unconditionally for tasks cancelled by
    an AnyIO cancel scope, which delivers the cancellation repeatedly until
    the task exits the scope.

    References: #13673

  • [asyncio] [bug] Fixed regression caused by #13548 where an asyncio cancellation
    delivered while the pool was closing a connection stranded by an
    exception raised within the _events.PoolEvents.connect() event
    handler would be suppressed, with the original exception raised in its
    place. The task would then not end as cancelled, and code that catches
    ordinary exceptions in order to retry would continue running after it
    had been cancelled. The CancelledError is now propagated.

    As part of this change, a variety of other cancellation scenarios were
    caught and repaired.

    References: #13678

postgresql

  • [postgresql] [bug] [reflection] Fixed issue in PostgreSQL ref...
Read more

2.1.4

Choose a tag to compare

@sqla-tester sqla-tester released this 07 Oct 17:34

2.1.4

Released: October 7, 2026

orm

  • [orm] [bug] [regression] Fixed regression caused by #13333 where assigning a
    MetaData object to the metadata attribute of a mapped
    Declarative class, in order to place that class's Table in a
    MetaData collection other than that of the
    _orm.registry, would emit a warning that the attribute name
    metadata should be left reserved. The warning is now emitted only
    when the metadata attribute is assigned to an object other than a
    MetaData.

    References: #13659

engine

  • [engine] [bug] [regression] Fixed regression from the 1.4 series where the DBAPI cursor used for a
    compiled statement that returns no rows, which includes DDL constructs
    such as CreateTable and DropTable as well as the
    SAVEPOINT, ROLLBACK TO SAVEPOINT and RELEASE SAVEPOINT statements emitted
    by _engine.Connection.begin_nested(), was not closed after
    execution, and was instead left to be released when garbage collected.
    The cursor is now closed immediately, as was the case in 1.3 and as is
    already the case for INSERT, UPDATE, DELETE and _sql.text()
    statements.

    This change is also backported to: 2.0.55

    References: #13640

  • [engine] [bug] Fixed issue in the
    _engine.Connection.execution_options.logging_token option
    where setting the value to None or another false value would raise
    AttributeError if a logging token had not already been established on
    that _engine.Connection, such as on a newly checked out
    connection, when the option was set at the _engine.Engine level,
    or when the token was reset to None more than once. Regression
    introduced in 2.0.30 as part of #11210.

    This change is also backported to: 2.0.55

    References: #13641

  • [engine] [bug] [regression] Fixed regression where iterating a Result directly would create
    reference cycles that prevented the Result along with the DBAPI
    cursor from being freed immediately, instead relying upon cyclic GC to free
    it; also fixed a similar issue when using scalar-returning methods such as
    Result.scalars(). The issues were introduced as part of a 2.1
    refactoring of Result into Cython code.

    References: #13639

  • [engine] [bug] Fixed issue where an exception raised by the rollback performed within
    _engine.Connection.close(), which was not otherwise classified as a
    disconnect, would leave the _engine.Connection open and its
    pooled DBAPI connection checked out, emitting a warning when later
    garbage collected; for an asyncio driver, the connection could not be
    closed at that point and was leaked. The DBAPI connection is now
    returned to the pool, which resets it as usual, invalidating it if that
    reset fails as well, and the original exception is raised.

    References: #13657

sql

  • [sql] [bug] Fixed issue where the
    UpdateBase.return_defaults.supplemental_cols parameter of
    UpdateBase.return_defaults() was not included in the cache key of
    an Insert or Update construct, causing a statement
    that used this parameter to share a cached compiled form with an otherwise
    identical statement that did not, or that used different supplemental
    columns. This would cause the supplemental RETURNING rows to be missing
    or incorrect depending on which form of the statement was compiled first.

    This change is also backported to: 2.0.55

  • [sql] [bug] [regression] Fixed regression where stringifying a statement that includes the
    _functions.aggregate_strings function, such as via str(),
    would raise TypeError. Additionally, compiling the function with the
    default dialect or with a third party dialect that doesn't provide its own
    rendering for this function now raises CompileError rather than
    TypeError.

    References: #13642

postgresql

  • [postgresql] [bug] Fixed regression where copying a _postgresql.DOMAIN datatype,
    as occurs when using TypeEngine.copy(), Table.to_metadata(),
    or when a Column referring to a _postgresql.DOMAIN is
    placed on a declarative mixin, would drop the
    _postgresql.DOMAIN.collation,
    _postgresql.DOMAIN.default,
    _postgresql.DOMAIN.constraint_name,
    _postgresql.DOMAIN.not_null and
    _postgresql.DOMAIN.check parameters, causing the
    CREATE DOMAIN statement emitted for the copied type to omit these
    elements. The regression was introduced in version 2.0.42.

    Additionally, copies of _postgresql.ENUM now retain the
    _types.Enum.sort_key_function,
    _types.Enum.create_constraint and
    _types.Enum.length parameters, and copies of
    _types.Enum now retain the
    _types.Enum.sort_key_function parameter, all of which were
    previously reset to their default values.

    This change is also backported to: 2.0.55

    References: #13644

  • [postgresql] [bug] [regression] Fixed regression in the asyncpg dialect where a failure to start a
    transaction, such as when an asyncpg connection is used from a different
    event loop than the one it was created on, would leave the failed asyncpg
    transaction in place, so that the subsequent rollback raised an
    InterfaceError that masked the original error.

    References: #13657

mssql

  • [mssql] [bug] Fixed issue where the Python true division operator / would truncate
    the fractional part of the divisor on SQL Server when the divisor's type
    was not numeric, such as when using a SQL function like NULLIF() that
    has no known return type. The divisor was rendered as CAST(... AS NUMERIC), which on SQL Server implies a scale of zero; the divisor is
    now rendered as (... + 0.0), the same approach used by the SQLite
    dialect, which promotes it to a decimal value without truncation.
    Pull request courtesy James Guthrie.

    This change is also backported to: 2.0.55

    References: #13631

misc

  • [bug] [ext] Fixed issue in the mutable_toplevel extension where the in-place
    |= operator on MutableDict and the in-place *= operator
    on MutableList did not emit change events, so that the
    modification was not detected and was not persisted on the next flush.
    These operators now invoke Mutable.changed() in the same way as
    the in-place set operators of MutableSet and the +=
    operator of MutableList.

    This change is also backported to: 2.0.55

    References: #13643

2.1.3

Choose a tag to compare

@sqla-tester sqla-tester released this 02 Oct 22:57

2.1.3

Released: October 2, 2026

orm

  • [orm] [bug] [regression] Fixed regression where columns delivered to a mapped class from an
    unmapped _orm.MappedAsDataclass mixin, or from a class decorated
    with _orm.unmapped_dataclass(), would not be placed in the
    _schema.Table in the order in which they were declared on the
    mixin; columns that had no dataclass default, such as a primary key column
    with no _orm.mapped_column.default, or which made use of
    _orm.mapped_column.default_factory, would be moved after the
    remaining columns of the mixin.

    References: #13634

sql

  • [sql] [bug] [regression] Fixed regression caused by the new implementation of
    ExecutableStatement.params() where parameter values established
    using this method would not be rendered when compiling the statement with
    the literal_binds compiler option, instead rendering NULL with a
    warning.

    References: #13635

2.1.2

Choose a tag to compare

@sqla-tester sqla-tester released this 02 Oct 04:16

2.1.2

Released: October 2, 2026

orm

  • [orm] [bug] [regression] Fixed regression caused by #5987 where the selectinload()
    loader strategy would ignore additional criteria present in the
    _orm.relationship.primaryjoin of a many-to-many relationship,
    such as a comparison against a column on the association table or on the
    parent table, loading related rows that should have been excluded. The
    omit_join optimization for many-to-many relationships is now only used
    when the primaryjoin consists solely of comparisons between the parent's
    primary key columns and the association table.

    References: #13626

engine

  • [engine] [bug] [regression] Fixed regression where result rows delivered by the DBAPI as a subclass of
    tuple would fail to be processed when the Cython extensions were in
    use, raising TypeError: Expected tuple. Such rows are now converted to
    a plain tuple, as was the case in 2.0. Rows delivered as a plain
    tuple, or as a sequence that is not a tuple at all such as
    asyncpg's Record, were not affected. Drivers such as the Databricks
    SQL connector return rows that are tuple subclasses.

    References: #13619

sql

  • [sql] [bug] The plain string portions of a _sql.tstring() construct are no
    longer scanned for bound parameter names in the :name format, and no
    longer interpret the \: escape sequence; the text is now rendered
    exactly as given. Previously, each string portion was parsed in the same
    way as _sql.text(), so that a colon within a quoted SQL string, such
    as 'time is :now', would be rendered as a bound parameter, failing at
    execution time.

    This change is a breaking change in the behavior of the newly introduced
    _sql.tstring() feature, as the former use of text()
    inadvertently provided a behavior that wasn't intended. In order to embed
    an explicit parameter in the SQL generated by _sql.tstring(),
    _sql.bindparam() may be used directly, e.g.
    tstring(t"SELECT {bindparam('p')}").

    References: #13616

schema

  • [schema] [bug] [regression] Fixed issue where the internal copies of Enum,
    _postgresql.DOMAIN and other SchemaType types produced
    when the type is adapted to a dialect-specific implementation would
    register themselves with the MetaData collection that is queried
    by MetaData.get_schema_objects(), in addition to the original type.
    As a new dialect-level copy is produced for each new Dialect
    instance, such as each time str() is called on a statement referring
    to a column using Enum, the collection would grow without bound
    and lead to excessive memory use. Only the type constructed by the user
    is now registered.

    References: #13625

2.1.1

Choose a tag to compare

@sqla-tester sqla-tester released this 25 Sep 14:59

2.1.1

Released: September 25, 2026

platform

  • [platform] [bug] Removed the legacy underscore-separated extra names such as
    mssql_pymssql and postgresql_psycopg from pyproject.toml.
    They normalize to the same names as the existing dash-separated extras,
    which is disallowed by PEP 685, and caused the 2.1.0 source
    distribution to fail to build with installers that enforce this rule,
    such as uv. The underscore spellings continue to work when installing,
    as installers normalize extra names before matching them.

    References: #13604

2.1.0

Choose a tag to compare

@sqla-tester sqla-tester released this 24 Sep 20:13

2.1.0

Released: September 24, 2026

orm

  • [orm] [feature] Added _orm.composite.column_template parameter to
    _orm.composite(). When the composite class is a dataclass, this
    parameter accepts a string template such as "person_%s", containing
    exactly one %s placeholder, that's used to generate column names for
    dataclass fields that don't otherwise have an explicit name, rather than
    using the bare field name. This removes the need to hand-write a
    _orm.mapped_column() for each field when the same composite dataclass
    is mapped multiple times on the same class with different column-name
    prefixes. Pull request courtesy Leonardo Rosa.

    References: #12575

  • [orm] [bug] Fixed issue where pickling an ORM object that had an instance level lazy
    loader established, such as when the _orm.raiseload() option is used,
    would emit a spurious warning regarding the loader containing additional
    criteria, if the object had itself been unpickled from a previous
    serialization. This would occur for objects that cross more than one
    serialization boundary, such as when using multiprocessing.

    This change is also backported to: 2.0.53

    References: #13574

  • [orm] [bug] Fixed issue where calling _orm.aliased() against an existing
    _orm.aliased() construct, without passing an explicit selectable,
    would disregard the selectable of the existing construct and produce an
    alias of the mapped table instead, if that selectable were anything other
    than a table or a plain subquery, leading to incorrect results and/or
    non-working queries.

    This includes _orm.aliased() against a _orm.with_polymorphic()
    construct, which would previously produce an alias of the base mapped
    class only, discarding the polymorphic selectable and additional mappers.
    The new construct now retains these, so that criteria against subclass
    attributes and the innerjoin and selectable parameters of
    _orm.with_polymorphic() take effect, and subclass columns are loaded
    up front. The SQL rendered for these constructs now includes the
    polymorphic selectable.

    This change is also backported to: 2.0.53

    References: #13583, #13584

  • [orm] [bug] Fixed issue where using _orm.aliased() with both the
    _orm.aliased.name and _orm.aliased.flat
    parameters, against an entity that is mapped to a join which includes
    anonymously named aliases, would embed the anonymous name symbol within
    the names generated for each element of the join, producing unusable
    SQL. An anonymously named element of the join is now aliased
    anonymously.

    This change is also backported to: 2.0.53

    References: #13583

sql

  • [sql] [bug] Fixed issue where calling _sql.CTE.alias() on a _sql.CTE
    that was itself produced by _sql.CTE.alias() would render SQL that
    referred to the name of the intermediate alias, which is not present in
    the WITH clause, rather than to the name of the original CTE, producing
    invalid SQL.

    This change is also backported to: 2.0.53

    References: #13583

postgresql

  • [postgresql] [usecase] PostgreSQL index reflection now reports so-called "invalid" indexes with
    postgresql_invalid=True in the Inspector's dialect_options.
    Reflected Index objects expose this state in the new
    read-only Index.reflect_only_elements mapping, separate from
    DDL options. Added new marker DialectKWArgConst.REFLECTED_ONLY
    which may be used by third party dialects for similar features within
    the DefaultDialect.construct_arguments registry. Pull request
    courtesy Max Azatian.

    References: #13577

mssql

  • [mssql] [bug] Adjusted the mssql+mssqlpython dialect for mssql-python version 1.15,
    which now binds all Python Decimal values as SQL_NUMERIC. The
    conversion of very large and very small Decimal values to strings,
    carried over from the pyodbc dialect, is no longer applied for this
    version of the driver, as the string values would otherwise be converted
    by SQL Server to the narrower numeric type of other Decimal values in
    the same statement, such as within a multiple-row INSERT, raising an
    arithmetic overflow error.

    References: #13585

  • [mssql] [bug] Fixed issue where Dialect.dbapi_version would raise
    AttributeError for the mssql+mssqlpython dialect, as the version
    was looked up using a version attribute that is not present on the
    mssql_python module, rather than __version__.

    References: #13585

oracle

  • [oracle] [usecase] Added support for oracledb's new terminate() feature, which allows for
    clean termination of an Oracle database connection in an asyncio context
    where the connection's state has fallen out of the event loop, and needs to
    be garbage collected. The feature is enabled automatically when using
    oracledb 26.0.0 or greater.

    This change is also backported to: 2.0.53

    References: #13578

2.0.54

Choose a tag to compare

@sqla-tester sqla-tester released this 15 Sep 21:07

2.0.54

Released: September 15, 2026

platform

  • [platform] [change] Binary wheels are no longer built for Python 3.7. PyPI now rejects wheel
    files whose filename does not begin with the normalized project name, and
    the packaging tools that can be installed on Python 3.7 do not produce
    such a filename. As a result, SQLAlchemy 2.0.44 was the last release to
    publish Python 3.7 wheels to PyPI, and releases 2.0.45 and later have
    been available on Python 3.7 only as a source distribution; the wheel
    builds for Python 3.7 are now removed. Python 3.7 remains supported by
    the 2.0 series.

  • [platform] [bug] Fixed issue where the Cython extensions were compiled without the
    freethreading_compatible directive, so that they did not declare
    themselves as safe to run without the GIL. On a free-threaded Python
    interpreter such as Python 3.13t or 3.14t, importing SQLAlchemy would
    cause the interpreter to re-enable the GIL, emitting a
    RuntimeWarning. The directive is now set when building for Python
    3.13 and above, and a test has been added which confirms that importing
    SQLAlchemy on a free-threaded build does not enable the GIL.

    References: #13592

2.0.53

Choose a tag to compare

@sqla-tester sqla-tester released this 14 Sep 20:37

2.0.53

Released: September 14, 2026

orm

  • [orm] [bug] Fixed issue where an expression passed to _orm.with_expression()
    that embedded a _sql.select(), such as a correlated
    _sql.exists(), would fail to populate the attribute correctly on
    the second and subsequent executions of an otherwise identical
    statement, when the _orm.query_expression() attribute was loaded
    by a relationship loader that emits a second query, i.e.
    _orm.selectinload(), _orm.lazyload() or
    _orm.immediateload().

    References: #13560

  • [orm] [bug] Fixed memory issue where mapped classes, along with their
    Table and _orm.Mapper objects, would not be garbage
    collected after the _orm.registry in which they were mapped had
    been disposed and dereferenced. The issue would occur for mappings that
    made use of _orm.relationship() together with constructs such as an
    Index established against an ORM-annotated expression.

    References: #13566

  • [orm] [bug] Fixed issue where pickling an ORM object that had an instance level lazy
    loader established, such as when the _orm.raiseload() option is used,
    would emit a spurious warning regarding the loader containing additional
    criteria, if the object had itself been unpickled from a previous
    serialization. This would occur for objects that cross more than one
    serialization boundary, such as when using multiprocessing.

    References: #13574

  • [orm] [bug] Fixed issue where calling _orm.aliased() against an existing
    _orm.aliased() construct, without passing an explicit selectable,
    would disregard the selectable of the existing construct and produce an
    alias of the mapped table instead, if that selectable were anything other
    than a table or a plain subquery, leading to incorrect results and/or
    non-working queries.

    This includes _orm.aliased() against a _orm.with_polymorphic()
    construct, which would previously produce an alias of the base mapped
    class only, discarding the polymorphic selectable and additional mappers.
    The new construct now retains these, so that criteria against subclass
    attributes and the innerjoin and selectable parameters of
    _orm.with_polymorphic() take effect, and subclass columns are loaded
    up front. The SQL rendered for these constructs now includes the
    polymorphic selectable.

    References: #13583, #13584

  • [orm] [bug] Fixed issue where using _orm.aliased() with both the
    _orm.aliased.name and _orm.aliased.flat
    parameters, against an entity that is mapped to a join which includes
    anonymously named aliases, would embed the anonymous name symbol within
    the names generated for each element of the join, producing unusable
    SQL. An anonymously named element of the join is now aliased
    anonymously.

    References: #13583

engine

  • [engine] [bug] [asyncio] [pool] Fixed issue where a DBAPI connection would be left open and unreachable
    if an exception were raised within the _events.PoolEvents.connect()
    or _events.PoolEvents.first_connect() event handlers, which is
    where Dialect.initialize() runs. The connection had been created
    but not yet associated with anything that could close it, so it was
    neither returned to the pool nor closed. For an asyncio driver in
    particular this could leak a server-side session for the life of the
    process, as the garbage collector is not able to close a connection that
    requires the event loop.

    References: #13548

sql

  • [sql] [bug] Fixed issue where calling _sql.CTE.alias() on a _sql.CTE
    that was itself produced by _sql.CTE.alias() would render SQL that
    referred to the name of the intermediate alias, which is not present in
    the WITH clause, rather than to the name of the original CTE, producing
    invalid SQL.

    References: #13583

schema

  • [schema] [bug] Fixed issue where reflecting a table with a foreign key constraint that
    names the same source column more than once, such as FOREIGN KEY (a, a) REFERENCES r (b, c) which is accepted by backends such as PostgreSQL,
    would raise ArgumentError and fail the reflection of the entire
    table. As ForeignKeyConstraint has no representation for this
    form, the constraint is now skipped with a warning, in the same way as one
    that names columns which are not present in the table, so that the
    remainder of the table is still reflected.

    Note that in the SQLAlchemy 2.1 series, full support for reflecting
    and constructing foreign key constraints with duplicated source columns
    has been added, with no warnings or skips.

    References: #13525

postgresql

  • [postgresql] [bug] Fixed bug in _reflection.Inspector.get_schema_names() for
    PostgreSQL where the query used to exclude system schemas relied on
    NOT LIKE 'pg_%', which treats the underscore as a SQL LIKE
    wildcard rather than a literal character. This caused user-created
    schemas that happen to start with "pg" followed by any other
    character, such as pgsql or pgstats, to be silently excluded
    along with actual system schemas like pg_catalog. Pull request
    courtesy Evan Rusackas.

    References: #13472

mysql

  • [mysql] [bug] Ensure that CREATE TABLE DDL statements for MySQL and MariaDB dialects
    render the table options in a deterministic order. Previously the order
    could change depending on the Python seed.

    References: #13523

sqlite

  • [sqlite] [usecase] [reflection] Implemented _engine.Inspector.get_table_options() for the SQLite
    dialect, which previously raised NotImplementedError. The method
    returns the sqlite_with_rowid and sqlite_strict dialect options
    for a table that was created using the WITHOUT ROWID and / or
    STRICT keywords. As a result, these keywords are now also present in
    _schema.Table.kwargs for a _schema.Table that is
    reflected using autoload, so that a table which is recreated from its
    reflected form, such as by Alembic's batch migration mode, renders the
    keywords again rather than silently dropping them.

    References: #13543

  • [sqlite] [bug] [reflection] Fixed issue in SQLite reflection where the name of a PRIMARY KEY,
    UNIQUE or FOREIGN KEY constraint would be reflected as None if
    the CONSTRAINT <name> clause were separated from the keyword that
    follows it by a newline rather than by spaces. As SQLite stores the
    CREATE TABLE statement as it was originally typed, this affected
    tables created from hand-written DDL that spans multiple lines. The
    regular expressions used to recover constraint names, as well as the
    ON UPDATE / ON DELETE, DEFERRABLE and INITIALLY options of
    a foreign key constraint, now accept any whitespace between tokens.

    References: #13528

  • [sqlite] [bug] [reflection] Reworked the regular expression that parses FOREIGN KEY constraints
    during SQLite CREATE TABLE reflection so that the referred column list
    is matched unambiguously avoiding potential exponential backtracking.
    Pull request courtesy of Javid Khan.

    References: #13530

mssql

  • [mssql] [bug] [reflection] Fixed issue in SQL Server reflection where TEXT and NTEXT columns
    would be reflected with a spurious length of 16 and 8, respectively. These
    are unlengthed LOB datatypes; the value originates from the
    sys.columns.max_length column, which reports the size of the in-row LOB
    pointer rather than a character length for these types. The reflected
    _mssql.TEXT and _mssql.NTEXT types now have a length
    of None, so that a reflected table emits valid DDL when re-created,
    which previously failed with "Cannot specify a column width on data type
    text". Pull request courtesy Sam Debruyn.

    References: #13451

oracle

  • [oracle] [usecase] Added support for oracledb's new terminate() feature, which allows for
    clean termination of an Oracle database connection in an asyncio context
    where the connection's state has fallen out of the event loop, and needs to
    be garbage collected. The feature is enabled automatically when using
    oracledb 26.0.0 or greater.

    References: #13578

tests

  • [tests] [bug] Altered the dialect reflection test
    test_check_constraint_parenthesized_expressions() so that it does not
    convert the reflected constraint to lowercase, which interferes with some
    third party dialect's representation of reflected check constraints.

    References: #13521

misc

  • [bug] [installation] Added the AUTHORS file to the set of license files included in the
    built wheel, where previously only the LICENSE file was present. As
    the text of LICENSE refers to AUTHORS for the list of copyright
    holders, the reference would not resolve for tools that inspect an
    installed distribution.

    R...

Read more