version 2.1.
-.. _change_10635:
-
-``Row`` now represents individual column types directly without ``Tuple``
---------------------------------------------------------------------------
-
-SQLAlchemy 2.0 implemented a broad array of :pep:`484` typing throughout
-all components, including a new ability for row-returning statements such
-as :func:`_sql.select` to maintain track of individual column types, which
-were then passed through the execution phase onto the :class:`_engine.Result`
-object and then to the individual :class:`_engine.Row` objects. Described
-at :ref:`change_result_typing_20`, this approach solved several issues
-with statement / row typing, but some remained unsolvable. In 2.1, one
-of those issues, that the individual column types needed to be packaged
-into a ``typing.Tuple``, is now resolved using new :pep:`646` integration,
-which allows for tuple-like types that are not actually typed as ``Tuple``.
+Introduction
+============
-In SQLAlchemy 2.0, a statement such as::
+This guide introduces what's new in SQLAlchemy version 2.1
+and also documents changes which affect users migrating
+their applications from the 2.0 series of SQLAlchemy to 2.1.
- stmt = select(column("x", Integer), column("y", String))
-
-Would be typed as::
-
- Select[Tuple[int, str]]
-
-In 2.1, it's now typed as::
-
- Select[int, str]
-
-When executing ``stmt``, the :class:`_engine.Result` and :class:`_engine.Row`
-objects will be typed as ``Result[int, str]`` and ``Row[int, str]``, respectively.
-The prior workaround using :attr:`_engine.Row._t` to type as a real ``Tuple``
-is no longer needed and projects can migrate off this pattern.
-
-Mypy users will need to make use of **Mypy 1.7 or greater** for pep-646
-integration to be available.
-
-Limitations
-^^^^^^^^^^^
-
-Not yet solved by pep-646 or any other pep is the ability for an arbitrary
-number of expressions within :class:`_sql.Select` and others to be mapped to
-row objects, without stating each argument position explicitly within typing
-annotations. To work around this issue, SQLAlchemy makes use of automated
-"stub generation" tools to generate hardcoded mappings of different numbers of
-positional arguments to constructs like :func:`_sql.select` to resolve to
-individual ``Unpack[]`` expressions (in SQLAlchemy 2.0, this generation
-produced ``Tuple[]`` annotations instead). This means that there are arbitrary
-limits on how many specific column expressions will be typed within the
-:class:`_engine.Row` object, without restoring to ``Any`` for remaining
-expressions; for :func:`_sql.select`, it's currently ten expressions, and
-for DML expressions like :func:`_dml.insert` that use :meth:`_dml.Insert.returning`,
-it's eight. If and when a new pep that provides a ``Map`` operator
-to pep-646 is proposed, this limitation can be lifted. [1]_ Originally, it was
-mistakenly assumed that this limitation prevented pep-646 from being usable at all,
-however, the ``Unpack`` construct does in fact replace everything that
-was done using ``Tuple`` in 2.0.
-
-An additional limitation for which there is no proposed solution is that
-there's no way for the name-based attributes on :class:`_engine.Row` to be
-automatically typed, so these continue to be typed as ``Any`` (e.g. ``row.x``
-and ``row.y`` for the above example). With current language features,
-this could only be fixed by having an explicit class-based construct that
-allows one to compose an explicit :class:`_engine.Row` with explicit fields
-up front, which would be verbose and not automatic.
-
-.. [1] https://github.com/python/typing/discussions/1001#discussioncomment-1897813
-
-:ticket:`10635`
+Please carefully review the sections on behavioral changes for
+potentially backwards-incompatible changes in behavior.
+General
+=======
.. _change_10197:
:ticket:`10197`
+New Features and Improvements - ORM
+====================================
+
+
+
+.. _change_9809:
+
+Session autoflush behavior simplified to be unconditional
+---------------------------------------------------------
+
+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.
+
+Previously, the session would only autoflush when executing ORM queries::
+
+ # 2.0 behavior - autoflush only occurred for ORM statements
+ session.add(User(name="new user"))
+
+ # This would trigger autoflush
+ users = session.execute(select(User)).scalars().all()
+
+ # This would NOT trigger autoflush
+ result = session.execute(text("SELECT * FROM users"))
+
+In 2.1, autoflush occurs for all statement executions::
+
+ # 2.1 behavior - autoflush occurs for all executions
+ session.add(User(name="new user"))
+
+ # Both of these now trigger autoflush
+ users = session.execute(select(User)).scalars().all()
+ result = session.execute(text("SELECT * FROM users"))
+
+This change provides more consistent and predictable session behavior across
+all types of SQL execution.
+
+:ticket:`9809`
+
.. _change_10050:
:ticket:`12168`
+New Features and Improvements - Core
+=====================================
+
+
+.. _change_10635:
+
+``Row`` now represents individual column types directly without ``Tuple``
+--------------------------------------------------------------------------
+
+SQLAlchemy 2.0 implemented a broad array of :pep:`484` typing throughout
+all components, including a new ability for row-returning statements such
+as :func:`_sql.select` to maintain track of individual column types, which
+were then passed through the execution phase onto the :class:`_engine.Result`
+object and then to the individual :class:`_engine.Row` objects. Described
+at :ref:`change_result_typing_20`, this approach solved several issues
+with statement / row typing, but some remained unsolvable. In 2.1, one
+of those issues, that the individual column types needed to be packaged
+into a ``typing.Tuple``, is now resolved using new :pep:`646` integration,
+which allows for tuple-like types that are not actually typed as ``Tuple``.
+
+In SQLAlchemy 2.0, a statement such as::
+
+ stmt = select(column("x", Integer), column("y", String))
+
+Would be typed as::
+
+ Select[Tuple[int, str]]
+
+In 2.1, it's now typed as::
+
+ Select[int, str]
+
+When executing ``stmt``, the :class:`_engine.Result` and :class:`_engine.Row`
+objects will be typed as ``Result[int, str]`` and ``Row[int, str]``, respectively.
+The prior workaround using :attr:`_engine.Row._t` to type as a real ``Tuple``
+is no longer needed and projects can migrate off this pattern.
+
+Mypy users will need to make use of **Mypy 1.7 or greater** for pep-646
+integration to be available.
+
+Limitations
+^^^^^^^^^^^
+
+Not yet solved by pep-646 or any other pep is the ability for an arbitrary
+number of expressions within :class:`_sql.Select` and others to be mapped to
+row objects, without stating each argument position explicitly within typing
+annotations. To work around this issue, SQLAlchemy makes use of automated
+"stub generation" tools to generate hardcoded mappings of different numbers of
+positional arguments to constructs like :func:`_sql.select` to resolve to
+individual ``Unpack[]`` expressions (in SQLAlchemy 2.0, this generation
+produced ``Tuple[]`` annotations instead). This means that there are arbitrary
+limits on how many specific column expressions will be typed within the
+:class:`_engine.Row` object, without restoring to ``Any`` for remaining
+expressions; for :func:`_sql.select`, it's currently ten expressions, and
+for DML expressions like :func:`_dml.insert` that use :meth:`_dml.Insert.returning`,
+it's eight. If and when a new pep that provides a ``Map`` operator
+to pep-646 is proposed, this limitation can be lifted. [1]_ Originally, it was
+mistakenly assumed that this limitation prevented pep-646 from being usable at all,
+however, the ``Unpack`` construct does in fact replace everything that
+was done using ``Tuple`` in 2.0.
+
+An additional limitation for which there is no proposed solution is that
+there's no way for the name-based attributes on :class:`_engine.Row` to be
+automatically typed, so these continue to be typed as ``Any`` (e.g. ``row.x``
+and ``row.y`` for the above example). With current language features,
+this could only be fixed by having an explicit class-based construct that
+allows one to compose an explicit :class:`_engine.Row` with explicit fields
+up front, which would be verbose and not automatic.
+
+.. [1] https://github.com/python/typing/discussions/1001#discussioncomment-1897813
+
+:ticket:`10635`
+
.. _change_11234:
the existing ``asyncpg.BitString`` type.
:ticket:`10556`
+
+