Skip to content

2.1.7

Choose a tag to compare

@sqla-tester sqla-tester released this 03 Nov 20:34
· 8 commits to main since this release

2.1.7

Released: November 3, 2026

orm

  • [orm] [feature] Added _orm.SessionTransaction.connections and
    _orm.SessionTransaction.bound_connection(), which return the
    connections a _orm.SessionTransaction has already acquired,
    without acquiring a new one. This allows, for example, checking whether
    any connection in the transaction has been invalidated before emitting
    further SQL. A SAVEPOINT transaction returned by
    _orm.Session.begin_nested() reports only the connections used
    within that SAVEPOINT; the root transaction returned by
    _orm.Session.get_transaction() reports all connections. The same
    accessors are available on _asyncio.AsyncSessionTransaction,
    returning _asyncio.AsyncConnection objects. Pull request
    courtesy Artur Shiriev.

    References: #13685

  • [orm] [bug] An informative InvalidRequestError is now raised when more than
    one PropComparator.of_type() loader option is applied along the
    same relationship using targets of differing classes, such as two
    _orm.aliased() constructs against different subclasses, which
    previously failed with a bare AssertionError during compilation.
    These options can't be combined since each target brings its own aliased
    copy of the base table; to load multiple subclasses along a single
    relationship, a single _orm.with_polymorphic() object that includes
    all of the target classes should be used.

    This change is also backported to: 2.0.56

    References: #13751

  • [orm] [bug] Fixed issue where criteria applied to a relationship using
    PropComparator.and_() within _orm.selectinload() or
    _orm.lazyload() would not be parenthesized against the
    relationship's join condition when the enclosing statement was retrieved
    from the compiled cache, so that criteria consisting of an
    _sql.or_() would render as a = ? AND b = ? OR c = ?. For
    _orm.lazyload(), this could load rows belonging to other parents into
    the collection. The issue typically appeared as a race condition in
    multithreaded applications, but could also occur within a single thread
    depending on the order in which statements were executed.

    This change is also backported to: 2.0.56

    References: #13753

  • [orm] [bug] [regression] Fixed regression caused by #13070 where using two or more
    orm.with_loader_criteria() options for different entities, where
    the criteria of each referred to the other entity, such as by using
    RelationshipProperty.Comparator.any() and
    RelationshipProperty.Comparator.has(), would fail with a
    RecursionError when compiling. A loader criteria option is now
    no longer applied to a SELECT that's nested at any level within its own
    criteria; previously this was only checked for the immediate SELECT
    within the criteria. This also repairs the same RecursionError for
    criteria which refer to each other using explicit subqueries, an issue
    also present in the 2.0 series. No backport is planned at the moment as
    this is an unusual use case.

    References: #13714

  • [orm] [bug] Fixed issue where _orm.Session.merge_all() did not disable autoflush
    while merging, as _orm.Session.merge() does, so that a lazy load
    emitted during the merge cascade could autoflush a partially merged
    object, leading to integrity errors or other failures.

    References: #13715

  • [orm] [bug] [regression] Fixed regression caused by #12168 where a collection-based
    _orm.relationship() using the
    orm_declarative_native_dataclasses feature with a custom
    _orm.relationship.collection_class such as
    _orm.attribute_keyed_dict() would raise an error if
    _orm.relationship.default_factory were set to the plain
    dict, list or set type that the collection class is based on,
    such as default_factory=dict for _orm.attribute_keyed_dict().
    The plain collection type is now accepted as the
    _orm.relationship.default_factory in addition to the exact
    collection class.

    References: #13718

  • [orm] [bug] [regression] Fixed regression caused by #5987 where the selectinload()
    loader strategy, when loading a many-to-many relationship against an
    aliased target, such as when using PropComparator.of_type() with a
    single-inheritance subclass or an aliased() construct, would render
    the join between the association table and the aliased target using the
    unaliased table name, producing invalid SQL.

    References: #13726

  • [orm] [bug] [regression] Fixed issue where a SAVEPOINT started by _orm.Session.begin_nested()
    would apply execution options to the connection a second time, causing
    options such as isolation_level to raise. Additionally, execution
    options passed to _orm.Session.connection() within a SAVEPOINT are
    now ignored with a warning if the connection is already present, as
    documented, where previously they were applied.

    References: #13744, #13745

  • [orm] [bug] [regression] Fixed regression caused by #12168 where a
    _orm.mapped_column() using
    _orm.mapped_column.use_existing_column along with
    _orm.mapped_column.default in an
    orm_declarative_native_dataclasses mapping would not persist the
    given default value. As the existing _schema.Column from the
    superclass table is mapped in this case, the default value would not take
    effect for INSERT, and the default from the superclass column, if any,
    would be used instead. The unit of work now delivers the dataclass
    default of an attribute that was never set to the INSERT statement in
    those cases where the mapped _schema.Column does not supply that
    same default, while the attribute continues to remain un-set until the
    object is flushed, so that the behaviors established by #12168
    are maintained.

    This includes a behavioral change for an attribute that is set to None
    explicitly, which the ORM treats as the default case for a column that has
    a default, as described at session_forcing_null. Such an attribute
    now receives the dataclass default of the subclass, consistent with an
    attribute that was never set, where previously the default of the
    superclass column would take effect.

    References: #13748

engine

  • [engine] [bug] Fixed issue where the _sa.create_engine.hide_parameters
    parameter was not honored when logging bound parameters for "side"
    statements that are executed separately from the main statement, such as
    pre-executed SQL expression column defaults, so that parameter values were
    written to the log in plain form. These parameters are now hidden in the
    same way as those of the main statement.

    This change is also backported to: 2.0.56

    References: #13724

  • [engine] [bug] Fixed issue where event listeners established on the
    _engine.Connection class itself, such as
    _events.ConnectionEvents.before_execute(), would not be invoked
    for connections procured from an _engine.Engine that did not
    itself have any event listeners associated with it.

    This change is also backported to: 2.0.56

    References: #13743

  • [engine] [bug] Fixed issue where URL.difference_update_query() would not remove
    any parameters when passed a generator or other single-use iterable, as
    the iterable was consumed twice internally. Pull request courtesy
    Huang-404-Q.

    This change is also backported to: 2.0.56

    References: #13757

  • [engine] [bug] [regression] Fixed regression where a checked-out pool connection that was garbage
    collected as part of a reference cycle would not be returned to the pool
    on Python 3.15, permanently consuming a slot in the pool. This occurred
    because Python 3.15 changed the garbage collector to clear weak references
    that have no callback only after weak reference callbacks have run, which
    interfered with the pool's weakref.finalize() based cleanup introduced
    in 2.1.0rc2. An ORM _orm.Session or _engine.Connection
    that was dropped without being closed while in a transaction was affected.

    References: #13738

sql

  • [sql] [bug] [oracle] [postgresql] Reworked the validation applied to the keyword phrases accepted by the
    _schema.ForeignKeyConstraint.ondelete,
    _schema.ForeignKeyConstraint.onupdate,
    _schema.ForeignKeyConstraint.initially and
    _schema.ForeignKeyConstraint.match parameters so that it is
    applied consistently on all backends. Previously, some of these phrases
    were checked against a fixed list of keywords, which rejected
    backend-specific syntaxes, while others were not validated at all,
    including MATCH on all backends, ON DELETE on the Oracle dialect,
    and the column list accepted by PostgreSQL for SET NULL / SET DEFAULT, allowing arbitrary text to be rendered into DDL. These phrases
    are now accepted as any sequence of SQL keywords, leaving it to the
    database to determine if the keywords are valid, and a
    CompileError is raised for a phrase that contains other
    characters such as punctuation or quotes. On PostgreSQL, the column list
    for SET NULL / SET DEFAULT is accepted as a comma-separated list
    of plain or double-quoted column names. Pull request courtesy
    basavaraj-sm05.

    This change is also backported to: 2.0.56

    References: #13727

  • [sql] [bug] [regression] Fixed regression caused by #12736 where the
    _sql.ColumnOperators.is_() and _sql.ColumnOperators.is_not()
    operators were assigned to the OperatorClass.COMPARISON operator
    class, which is not present on all datatypes. As these operators are valid
    for every SQL datatype, they are now part of OperatorClass.BASE,
    so that expressions such as hstore_column.is_not(None) no longer emit a
    deprecation warning.

    References: #13719

typing

  • [typing] [bug] Fixed typing issue where constructs that infer their Python type from a
    SQL type argument could not be assigned to a wider declared type, such as
    x: Column[int | None] = Column(Integer) or x: ColumnElement[int | None] = cast(expr, Integer), which raised an incompatible argument error
    under mypy and an incompatible assignment error under pyright. The
    construct still infers its type from the given SQL type, where
    Column(Integer) is typed as Column[int]; this type may now be
    widened by the declared type, which is also where nullability is
    indicated. This applies to _schema.Column, cast(),
    _sql.try_cast(), type_coerce(), bindparam(),
    _sql.literal(), _sql.column(), _sql.literal_column(),
    _sql.label(), _expression.ColumnElement.cast(),
    Function, _types.ARRAY and
    _postgresql.ARRAY, including when these are passed to ORM
    constructs such as _orm.column_property(), _orm.deferred()
    and _orm.query_expression() that are assigned to a wider
    Mapped[] annotation.

    This includes assignment of a _schema.Column to a wider
    _schema.Named annotation within a _schema.TypedColumns
    class, e.g. x: Named[int | None] = Column(SmallInteger), for which
    _schema.Named is now covariant.

    References: #13654

asyncio

  • [asyncio] [bug] Fixed issue where an asyncio cancellation delivered to a task waiting for
    a connection from AsyncAdaptedQueuePool would be lost on Python
    3.11, if a connection were returned to the pool in the same event loop
    iteration in which the task was cancelled. The task would receive the
    connection and continue running as though it had not been cancelled.
    This was due to a known issue in asyncio.wait_for() which is fixed in
    Python 3.12; on Python 3.11, the pool now uses asyncio.timeout() in
    place of asyncio.wait_for() when waiting for a connection. The issue
    remains on Python 3.10 and earlier, where asyncio.timeout() is not
    available.

    This change is also backported to: 2.0.56

    References: #13749

mssql

  • [mssql] [bug] [reflection] Reflection of SQL Server local temporary tables, such as #tmp, now
    supports tables of the same name existing in multiple sessions at once,
    each session reflecting its own table. Previously, the table was located
    without the use of the object_id() function, which is what limits the
    lookup to the current session, so that same-named tables in other sessions
    would interfere with reflection. The change also eliminates blocking and
    deadlocks which could occur when reflecting a temporary table while other
    sessions were creating temporary tables of a similar name within a
    transaction.

    This change is also backported to: 2.0.56

    References: #13754