Mike Bayer [Sat, 27 Jun 2026 20:20:17 +0000 (16:20 -0400)]
set PYTHONUTF8=1 for attestation step to fix encoding on Windows
pypi_attestations sign writes attestation JSON files using the Windows
default encoding (CP1252) rather than UTF-8, causing twine to fail with
a UnicodeDecodeError when reading them back. PYTHONUTF8=1 forces UTF-8
for all file I/O on Windows.
Mike Bayer [Sat, 27 Jun 2026 19:59:35 +0000 (15:59 -0400)]
continue-on-error for upload-release-assets to handle already_exists on re-runs
When re-running the wheel build, wheels already uploaded to the GitHub release
cause upload-release-assets to fail with already_exists. With continue-on-error,
the attestation and PyPI upload steps still proceed since the wheel files remain
in ./wheelhouse/ regardless.
Mike Bayer [Sat, 27 Jun 2026 19:50:02 +0000 (15:50 -0400)]
add shell: bash to attestations step to fix glob expansion on Windows
PowerShell does not expand ./wheelhouse/* globs, causing pypi_attestations
sign to receive a literal asterisk and fail. Explicit bash shell ensures
glob expansion works on all platforms.
Mike Bayer [Sat, 27 Jun 2026 19:46:41 +0000 (15:46 -0400)]
fall back to twine upload without attestations if attestation signing fails
On some platforms (e.g. windows-11-arm) pypi-attestations cannot install
due to missing cryptography binary wheels. Use continue-on-error on the
attestation step and conditionally pass --attestations to twine only when
signing succeeded.
Mike Bayer [Sat, 27 Jun 2026 19:17:29 +0000 (15:17 -0400)]
fix create-wheels.yaml for cibuildwheel 4.x and release asset upload
Remove CIBW_ENABLE cpython-freethreading which is no longer a valid enable
group in cibuildwheel 4.x; free-threaded builds are now included by default
when specifying cp314t-* in CIBW_BUILD.
Change contents permission from read to write so that the upload-release-assets
step can attach wheels to the GitHub release.
Mike Bayer [Sat, 27 Jun 2026 16:15:59 +0000 (12:15 -0400)]
fixes for m2momitjointest
in 808fd28297f36bf932443bae77ca5bb16bcbd4dd , the new test suite
created per-column `ForeignKey` constructs instead of composite
ForeignKeyConstraint objects, leading to failures on all backends
other than SQLite. The test now runs with backend, and also does not
need AssertsCompiledSQL directives.
Mike Bayer [Fri, 26 Jun 2026 14:09:38 +0000 (10:09 -0400)]
wrap before/after_cursor_execute event hooks in error handling
Expanded try/except error handling in _exec_single_context(),
_exec_insertmany_context(), and _cursor_execute() to encompass the
before_cursor_execute and after_cursor_execute event hooks. This
ensures that exceptions raised within these hooks, including
BaseException subclasses such as asyncio.CancelledError, are
properly handled via _handle_dbapi_exception(), providing correct
connection invalidation and pool notification.
Also added a guard in _handle_dbapi_exception to avoid
double-wrapping exceptions that are already StatementError
instances, which could occur when _cursor_execute's error handling
propagates up through _execute_context.
As part of this change, DBAPI errors raised from within these event
hooks will now be wrapped as SQLAlchemy exceptions.
Mike Bayer [Wed, 24 Jun 2026 12:49:30 +0000 (08:49 -0400)]
override get_select_precolumns() in StrSQLCompiler
Fixed issue where :meth:`_sql.Select.get_final_froms` would emit a
deprecation warning when the statement made use of the PostgreSQL-specific
expression argument to :meth:`_sql.Select.distinct`; the same spurious
warning would be emitted when stringifying such a statement without
explicitly using a PostgreSQL dialect. The fix ensures that this 1.4-era
warning is suppressed under both 2.0 and 2.1.
Note that under SQLAlchemy 2.1, passing an expression to
:meth:`_sql.Select.distinct` is deprecated overall, and is replaced by a
new PostgreSQL-specific construct (see :ticket:`12342`).
dxbjavid [Mon, 22 Jun 2026 15:30:40 +0000 (11:30 -0400)]
fix catastrophic backtracking in mysql index comment reflection regex
Improved the regular expression used to parse index COMMENT clauses
in MySQL SHOW CREATE TABLE reflection to use an unambiguous
single-quoted-string pattern; the previous pattern was theoretically
subject to backtracking on malformed input, though such input is not
producible by MySQL itself. Fix courtesy of Javid Khan.
cjc0013 [Thu, 11 Jun 2026 21:23:04 +0000 (17:23 -0400)]
Add explicit USING support to DELETE
Added :meth:`_sql.Delete.using`, allowing explicit FROM expressions such as
joins to be rendered in backend-specific multiple-table DELETE forms
including MySQL/MariaDB ``DELETE .. USING``. Pull request courtesy
cjc0013.
Mike Bayer [Mon, 22 Jun 2026 15:36:02 +0000 (11:36 -0400)]
use @classmethod per pytest guidance
Fixed class-scoped pytest fixtures that were defined as instance methods
using ``self``, which is deprecated as of pytest 9.1 and will be removed in
pytest 10. Fixtures are now decorated with a compatibility ``@classmethod``
decorator and use ``cls`` as the first parameter.
dxbjavid [Wed, 17 Jun 2026 12:49:06 +0000 (08:49 -0400)]
quote driver name and pass-through keys in pyodbc connect string
Tightened the construction of the ODBC connection string in the pyodbc
connector (as well as the mssql-python connector in 2.1) so that the
driver name, the names of pass-through connection parameters, and values
containing ``}`` are brace-quoted. Previously a ``}`` in the driver name
or in a pass-through value, or a ``;`` in the name of a pass-through
parameter, could close the surrounding token early and allow the
remainder of the string to be interpreted as additional connection
attributes. Pull request courtesy dxbjavid.
Mike Bayer [Wed, 17 Jun 2026 22:06:56 +0000 (18:06 -0400)]
Fix is_pep695 misidentifying Annotated[TypeAliasType] as PEP 695
The is_pep695() function incorrectly identified
Annotated[TypeAliasType, ...] as a PEP 695 type alias because
Annotated's __origin__ attribute returns the first type argument
(the TypeAliasType) rather than Annotated itself. This caused
_init_column_for_annotation to crash with AttributeError when
attempting to access __value__ on the Annotated wrapper.
Added a check for is_pep593() before recursing through __origin__
in is_pep695(), so Annotated types are correctly excluded.
Oliver Parker [Wed, 17 Jun 2026 15:15:18 +0000 (11:15 -0400)]
Improve performance of selectinload result handling by up to ~30%
* in selectinloader, dont use Bundle() to represent the PK portion
* use more efficient mapper._state_ident_getter() method in selectinloader
which pre-resolves keys and only calls upon _get_state_attr_by_column when
an attribute is not locally present
* removed use of groupby() + lambda against Row objects in subqueryloader; converts
to tuple and builds lists via append()
Adds tests pinning behavior of the rewritten result handling: the uselist=False multiple-rows warning, and many-to-one loads where the foreign key value matches no row or is NULL.
Benchmarked on an in-memory SQLite database (median of 30 runs, ms):
Oliver Parker [Mon, 15 Jun 2026 14:41:06 +0000 (10:41 -0400)]
Fixes: 13363 Process ORM result rows as plain tuples without Row construction
ORM result row fetching now processes rows as plain tuples rather than
constructing :class:`.Row` objects, as ORM loaders use position-based
access and do not require the :class:`.Row` interface. :class:`.Row`
construction is still used when engine-level debug logging is enabled so
that individual rows can be logged. Benchmarks show a 3-16% improvement in
ORM entity load times depending on query shape. Pull request courtesy
Oliver Parker.
Adds Result._all_interim_rows(), which returns the remaining rows as processed plain tuples, applying result processors and tuple filters but skipping Row object construction. ORM loading uses this for its row fetch; its row getters are position-based itemgetters that accept any tuple-like row. Results that require row logging or have scalar sources fall back to Row construction.
Adds tests covering the new behavior: rows are plain tuples with result processors applied, and Row construction still occurs when engine-level row logging is enabled, at both the Result and ORM loading level.
Benchmarked on an in-memory SQLite database with this change alone (median of 30 runs, ms); this path is used by all ORM entity loads:
plain_small (500 rows x 5 cols) 2.43 -> 2.28 -6%
plain_wide (2000 rows x 25 cols) 8.17 -> 7.24 -11%
joined_o2m (500 x 10) 18.56 -> 16.95 -9%
selectin_o2m (500 x 10) 18.45 -> 17.79 -4%
selectin_nested (50 x 10 x 10) 18.67 -> 17.33 -7%
selectin_m2m (500 x 10) 12.70 -> 11.53 -9%
selectin_m2o (5000 -> 200) 15.83 -> 15.29 -3%
selectin_o2m_few_big (20 x 500) 32.96 -> 31.29 -5%
subquery_o2m (500 x 10) 21.51 -> 18.16 -16%
dxbjavid [Fri, 12 Jun 2026 11:19:04 +0000 (07:19 -0400)]
fix backtracking hang in hstore literal parser
Fixed regular expression in the pure Python hstore result processor,
used when ``use_native_hstore=False`` is set, which could hang on
malformed hstore text containing unterminated quoted segments with
backslashes. Pull request courtesy dxbjavid.
Mike Bayer [Fri, 5 Jun 2026 20:02:02 +0000 (16:02 -0400)]
factor single-table reflection wrappers into common mixin
Introduced _BackendsMultiReflection mixin in engine/default.py
that provides the get_columns(), get_pk_constraint(),
get_foreign_keys(), get_indexes(), get_unique_constraints(),
get_check_constraints(), get_table_comment(), and
get_table_options() single-table methods, each delegating to
the corresponding get_multi_* method with
filter_names=[table_name].
PostgreSQL, Oracle, and MSSQL dialects now inherit from this
mixin instead of duplicating the wrapper pattern. Oracle
retains its _value_or_raise() override which applies
normalize_name() for case-folding. MSSQL overrides
get_unique_constraints(), get_check_constraints(), and
get_table_options() with NotImplementedError since it has no
native get_multi_* for those yet.
Mike Bayer [Tue, 9 Jun 2026 18:47:46 +0000 (14:47 -0400)]
allow rollback within _prepare_impl on twophase prepare failure
When tpc_prepare() raised during SessionTransaction._prepare_impl(),
the error handler's call to self.rollback() was blocked by the
@declare_states decorator, which had set _next_state to
CHANGE_IN_PROGRESS. This caused IllegalStateChangeError to be raised
instead of the original database exception, masking the real error
and preventing proper cleanup.
Used _expect_state(SessionTransactionState.CLOSED) to temporarily
allow the rollback state transition, matching the existing pattern
used in commit() for the close() call.
Repaired bug introduced in :ticket:`13229` where a two-phase
transaction recovery would not return the correct transaction
identifier when generating the identifiers using the ``xid()``
method of the psycopg connection.
Mike Bayer [Fri, 5 Jun 2026 16:35:35 +0000 (12:35 -0400)]
use trusted publishing for PyPI wheel uploads
Replace token-based PyPI authentication with OIDC trusted publishing.
Add workflow-level id-token: write permission, generate PEP 740
attestations using pypi-attestations, and upload with
twine --attestations. Removes the pypi_token secret dependency.
Removed the legacy ``include_columns`` key from the dictionary returned
by the index reflection methods of some dialects.
This information is now part of the ``dialect_options`` dictionary under the key
``{dialect_name}_include``, such as ``postgresql_include`` or ``mssql_include``.
Gaurav Sharma [Wed, 3 Jun 2026 12:52:09 +0000 (08:52 -0400)]
Implement native multi-table reflection API for the mssql dialect
### Description
Adds 5 native `get_multi_*` reflection methods (columns, pk, fk, indexes, table_comment) for the MSSQL dialect, replacing the per-table loop in `_default_multi_reflect`. Single-table methods now delegate to the multi versions (PG/Oracle pattern); legacy per-table SQL is retained as `_internal_get_*` helpers, used only for tempdb reflection.
Not implemented here: `get_multi_unique_constraints`, `get_multi_check_constraints`, `get_multi_table_options` -MSSQL has no single-table counterparts to delegate from. Happy to add as a follow-up.
### Performance
Measured with `test/perf/many_table_reflection.py` against SQL Server 2022 (Docker, localhost, pyodbc + ODBC Driver 18) on a 250-table fixture, 15-50 cols, with PKs/FKs/indexes/comments:
Mike Bayer [Wed, 3 Jun 2026 13:46:19 +0000 (09:46 -0400)]
send execution options to connection also
Session level :paramref:`_orm.Session.execution_options` now take
effect for Core level SQL emitted by unit of work operations, in
addition to their existing use within ORM statement executions.
This is to provide for Core options such as
:paramref:`_engine.Connection.execution_options.schema_translate_map`
to be applicable to a :class:`.Session` overall.
Mike Bayer [Thu, 4 Jun 2026 14:17:06 +0000 (10:17 -0400)]
Register func.any(), func.all(), func.some() as collection aggregates
Added CollectionAggregateFunction base class that sets
_is_collection_aggregate = True, and registered any_, all_, some_
as subclasses so that func.any(), func.all(), and func.some() correctly
prevent operator flipping on negation. Previously ~(col == func.any(arr))
would incorrectly compile to col != any(arr) instead of
NOT (col = any(arr)), which has different semantics for collection
aggregate comparison modifiers.
Also extended the _construct_for_op guard to check both left and right
operands for _is_collection_aggregate, since func.any(arr) can appear
on either side of a comparison unlike the standalone any_() construct
which auto-reverses operands.
Oliver Parker [Wed, 3 Jun 2026 06:51:13 +0000 (02:51 -0400)]
Perf/conditional unique selectinload
Optimized :func:`_orm.selectinload` to skip the ``.unique()`` call on inner
result sets when no nested :func:`_orm.joinedload` on a collection is
present. The uniquing pass is only required when a joined eager load
inflates rows due to a one-to-many or many-to-many JOIN; in the common case
of a leaf selectin load, rows are already unique by construction and the
per-row hashing overhead can be avoided. As a side effect, ``yield_per``
set in a ``do_orm_execute`` event for a :func:`_orm.selectinload`
relationship load no longer raises ``InvalidRequestError`` when no nested
collection joinedload is in effect, since ``.unique()`` is no longer called
in that path. Pull request courtesy Oliver Parker.
`_SelectInLoader._load_via_parent` and `_load_via_child` currently call
`.unique()` unconditionally on the inner `Result`. The uniqueness pass is only
required when a nested `joinedload` on a collection is in effect — in that case
the `JOIN` inflates rows (one row per `(child, grandchild)` instead of one per
child) and the outer `groupby` would produce duplicated entries without dedup.
`loading.instances()` already signals exactly this condition: it sets
`Result._unique_filter_state` to a `require_unique` guard when the inner
compile state has `multi_row_eager_loaders=True`. When that flag is unset (no
nested collection `joinedload`), `_unique_filter_state` stays `None`, meaning
the inner query produces unique rows by construction:
- `omit_join` 1:N — one row per child
- `omit_join` M2O — one row per parent
- `omit_join` M2M — one row per (parent, entity)
- `load_with_join` (non-omit) — one row per (parent, child)
This PR makes the `.unique()` call conditional, via a new `_has_unique_filter`
property on `Result` that exposes this state without reaching into the private
`_unique_filter_state` attribute directly:
```python
if result._has_unique_filter:
result = result.unique()
```
Per-row hashing in `_iterator_getter` is avoided in the common leaf-load case.
On an in-memory SQLite bench (2000 parents × 5 children + M:N tags, Python
3.14, n=1000 iterations):
| Workload | before avg | after avg | delta | before sd | after sd |
|---|---|---|---|---|---|
| selectin children (1:N) | 56.0 ms | 41.4 ms | **−26%** | 5.2 ms | 1.9 ms |
| selectin tags M2M | 25.6 ms | 20.0 ms | **−22%** | 4.2 ms | 3.1 ms |
| joined children (1:N) | 42.9 ms | 37.6 ms | ~0% | — | — |
| plain parent load | 4.2 ms | 3.6 ms | ~0% | — | — |
**Behaviour change:** `yield_per` set in a `do_orm_execute` event for a
relationship load no longer raises
`InvalidRequestError("Can't use yield_per in conjunction with unique")` for
`selectinload` without a nested collection `joinedload` — because `.unique()`
is no longer called in that path. `immediateload` is unaffected (it still calls
`.unique()` unconditionally).
sebastianbreguel [Sun, 31 May 2026 20:12:19 +0000 (16:12 -0400)]
Hoist loop-invariant set intersection in _get_display_froms
Fixes #13336.
`SelectState._get_display_froms` recomputed a loop-invariant `_cloned_intersection(...)` once per FROM element in each of the three correlation comprehensions, making each branch O(N²) in the number of FROM elements. This hoists the call so it runs once, which is O(N).
`_cloned_intersection` / `_cloned_difference` are pure and return a set, and neither argument changes during the comprehension, so the result is identical. A function-level benchmark asserts `old == new` at every N (full numbers in #13336), and `test/sql/` plus the ORM compilation/query tests pass: 7442 passed, 359 skipped. Net -14 lines.
Per the issue discussion, no changelog entry is included.
### Checklist
This pull request is:
- [x] A short code fix
- Issue with a runnable demonstration: #13336
- Behavior-preserving (no logic change), so it is covered by the existing `test/sql/` and ORM compilation/query suites rather than adding new tests.
me-saurabhkohli [Fri, 29 May 2026 20:03:40 +0000 (16:03 -0400)]
Add ambiguous column support to SimpleResultMetaData
Fixed issue where :meth:`.Result.freeze` would lose track of ambiguous
column names present in the original :class:`.CursorResult`, causing
key-based access on the thawed result to silently return a value instead of
raising :class:`.InvalidRequestError`. The
:class:`.SimpleResultMetaData` now accepts and propagates ambiguous key
information so that frozen, thawed, and pickled results raise consistently
for duplicate column names. Pull request courtesy Saurabh Kohli.
Mike Bayer [Thu, 28 May 2026 14:25:39 +0000 (10:25 -0400)]
allow backref named 'metadata' to not break _metadata_for_cls
Fixed regression caused by :ticket:`8068` where a ``backref``
named ``'metadata'`` on a mapped class would cause an
``AssertionError`` when the class also used string-based
relationship references (e.g. ``secondary="some_table"``).
The ``_metadata_for_cls()`` helper now checks
``isinstance(meta, MetaData)`` as a condition rather than
asserting, falling back to ``registry.metadata`` when the
class attribute has been overwritten by a backref.
A warning is now emitted when a Declarative attribute name is named
``metadata`` or ``registry``. Previously, no warning was emitted for
``registry``, and using the name ``metadata`` would raise an
InvalidRequestError. Since these names can be used for attributes
that are mapped as backrefs or using imperative mappings, usage
under Declarative has been relaxed for ``metadata`` but also warns
for both names as they may have unintended interactions with the
Declarative reserved names.
cjc0013 [Mon, 25 May 2026 16:46:50 +0000 (12:46 -0400)]
Fix lambda statements with non-lambda criteria
Fixed issue where :class:`_sql.StatementLambdaElement` would proxy
attribute access through the cached "expected" expression rather than the
resolved expression, causing stale closure-bound parameter values to be
used when a lambda statement was extended with non-lambda criteria such as
an additional ``.where()`` clause. Courtesy cjc0013.
Arya Rizky [Tue, 12 May 2026 19:08:02 +0000 (15:08 -0400)]
Fix subqueryload losing .and_() criteria when combined with of_type()
Fixed issue where :func:`_orm.subqueryload` combined with
:meth:`.PropComparator.of_type` and :meth:`.PropComparator.and_` would
silently drop the additional filter criteria, causing all related objects
to be loaded instead of only those matching the filter. The
:class:`.LoaderCriteriaOption` was being constructed against the base
entity rather than the effective entity indicated by
:meth:`.PropComparator.of_type`. Pull request courtesy Arya Rizky.
bekapono [Mon, 18 May 2026 16:29:50 +0000 (12:29 -0400)]
omit_join optimization for selectinload on many-to-many relationships
The :func:`.selectinload` loader strategy now selects the ``omit_join``
optimization for many-to-many non-self-referential relationships, reducing
the number of joins in the secondary SELECT by selecting from the secondary
table directly rather than joining back to the parent entity. ``omit_join``
is enabled automatically when the join condition determines that the
secondary table's foreign keys fully cover the parent's primary key. As
always, ``omit_join`` can be disabled by setting
:paramref:`.relationship.omit_join` to ``False``. Pull request courtesy
bekapono.
proto-atlas [Mon, 25 May 2026 19:14:40 +0000 (15:14 -0400)]
Fix Session bulk mappings typing for mapped classes
Fixes #9256.
This updates the annotations for Session.bulk_insert_mappings() and Session.bulk_update_mappings().
The docstrings and runtime behavior already allow either a mapped class or a Mapper object, but the previous annotations only accepted Mapper[Any].
This patch switches those arguments to the existing _EntityBindKey alias, which matches the inputs accepted by _class_to_mapper(): mapped classes and Mapper objects, but not AliasedClass or AliasedInsp.
I also updated the internal _bulk_save_mappings() annotation so the public methods and the private helper stay consistent. The scoped_session proxy output has been kept in sync with tools/generate_proxy_methods.py, and the generator check passes.
I added a typing regression test covering both mapped classes and Mapper objects for the two bulk mapping methods. I confirmed that the mapped-class cases fail with the old annotation and pass with this change.
I could not run the full typing suite locally because my local Python 3.12 environment does not include string.templatelib. I only skipped typed_queries.py; that file is expected to be covered by SQLAlchemy's Python 3.14 mypy CI job.
Mike Bayer [Fri, 22 May 2026 20:01:10 +0000 (16:01 -0400)]
dont produce side effects for do_orm_execute
Fixed issue where the presence of a :meth:`.SessionEvents.do_orm_execute`
event hook would cause internal execution options such as ``yield_per`` and
loader-specific state from the first ``orm_pre_session_exec`` pass to leak
into the second pass, leading to errors when using relationship loaders
such as :func:`.selectinload` and :func:`.immediateload`. The execution
options passed to the second compilation pass are now based on the original
options plus only the explicit updates made via
:meth:`.ORMExecuteState.update_execution_options` within the event hook.
Federico Caselli [Tue, 19 May 2026 21:39:49 +0000 (23:39 +0200)]
user_defined_options returns memoized options
Updated the attribute :attr:`_orm.ORMExecuteState.user_defined_options` to
include options that were added to the statement before calling
:meth:`.Select.with_only_columns` or :meth:`_orm.Query.with_entities`.
Mike Bayer [Wed, 20 May 2026 19:59:10 +0000 (15:59 -0400)]
implement _post_inspect for AliasedInsp
Fixed issue where using :func:`_orm.with_polymorphic` on a leaf class (a
subclass with no further descendants) or a non-inherited class would fail
with an ``AttributeError`` when used in an ORM statement, due to
:func:`_orm.configure_mappers` not being triggered implicitly. The fix
ensures that :class:`.AliasedInsp` participates in the ``_post_inspect``
hook, triggering mapper configuration during ORM statement compilation.
WiktorB2004 [Wed, 20 May 2026 20:05:41 +0000 (16:05 -0400)]
Fix ExcludeConstraint not forwarding info to parent constructor
Fixed issue where the :class:`.ExcludeConstraint` construct did not
correctly forward the :paramref:`.ExcludeConstraint.info` parameter to
the superclass, causing user-defined metadata to be lost. Pull request
courtesy Wiktor Byrka.
Fixed issue where using :func:`_orm.joinedload` with
:meth:`.PropComparator.of_type` targeting a joined-table subclass combined
with :meth:`.PropComparator.and_` referencing a column on that subclass
would generate invalid SQL, where the subclass column was not adapted to
the subquery alias. Pull request courtesy Joaquin Hui Gomez.
OSS Contributor [Mon, 23 Mar 2026 14:44:40 +0000 (10:44 -0400)]
Fix floordiv (//) for float/numeric by int with div_is_floordiv dialects
Fixed issue where floor division (``//``) between a :class:`.Float` or
:class:`.Numeric` numerator and an :class:`.Integer` denominator would omit
the ``FLOOR()`` SQL wrapper on dialects where
:attr:`.Dialect.div_is_floordiv` is ``True`` (the default, including
PostgreSQL and SQLite). ``FLOOR()`` is now applied if either the
denominator or the numerator is a non-integer, so that expressions such as
``float_col // int_col`` render as ``FLOOR(float_col / int_col)`` instead
of the incorrect ``float_col / int_col``. Pull request courtesy r266-tech.
mattip [Wed, 20 May 2026 17:47:17 +0000 (13:47 -0400)]
Fix trivial PyPy failures
<!-- Provide a general summary of your proposed changes in the Title field above -->
### Description
Fixes: #13274
References: #9154
There were two relatively causes to some of the ~21 failures on PyPy:
- weakrefs may be deleted but the objects not finalized on PyPy. This manifests as `ref.obj() is None` I added a test for the `release()` case that also failed on CPython before the fix.
- a condition added in 2022 for missing sqllite3 behaviour is no longer necessary, and is now causing a failure
In order to run the changes in CI, I added PyPy to the PR CI run. Before merging I will revert that change. There are still a number of failures with PyPy around different error messages, different inspect.signatures and one sticky problem with the pure-python datetime.py that actually comes from CPython. I will continue to work on them, but they are not specific to sqlalchemy.
Note the CI run is ~6 minutes where the CPython ones are ~3 minutes. This is expected, since PyPy's JIT does not kick in on short tests, and the base compiler is about 2x slower.
### Checklist
<!-- go over following points. check them with an `x` if they do apply, (they turn into clickable checkboxes once the PR is submitted, so no need to do everything at once)
-->
This pull request is:
- [ ] A documentation / typographical / small typing error fix
- Good to go, no issue or tests are needed
- [x] A short code fix
- please include the issue number, and create an issue if none exists, which
must include a complete example of the issue. one line code fixes without an
issue and demonstration will not be accepted.
- Please include: `Fixes: #<issue number>` in the commit message
- please include tests. one line code fixes without tests will not be accepted.
- [ ] A new feature implementation
- please include the issue number, and create an issue if none exists, which must
include a complete example of how the feature would look.
- Please include: `Fixes: #<issue number>` in the commit message
- please include tests.
Karolina Surma [Tue, 19 May 2026 14:20:38 +0000 (10:20 -0400)]
Adjust TypeError message to Python 3.15
<!-- Provide a general summary of your proposed changes in the Title field above -->
### Description
The `.fromisoformat()` error message tested in `test_no_string()` changed in Python 3.15, this fixes the test.
See #13308 for the `rel_2_0` branch.
### Checklist
<!-- go over following points. check them with an `x` if they do apply, (they turn into clickable checkboxes once the PR is submitted, so no need to do everything at once)
-->
This pull request is:
- [ ] A documentation / typographical / small typing error fix
- Good to go, no issue or tests are needed
- [x] A short code fix (in a test, therefore I didn’t create an issue)
- please include the issue number, and create an issue if none exists, which
must include a complete example of the issue. one line code fixes without an
issue and demonstration will not be accepted.
- Please include: `Fixes: #<issue number>` in the commit message
- please include tests. one line code fixes without tests will not be accepted.
- [ ] A new feature implementation
- please include the issue number, and create an issue if none exists, which must
include a complete example of how the feature would look.
- Please include: `Fixes: #<issue number>` in the commit message
- please include tests.
Mike Bayer [Tue, 19 May 2026 13:40:01 +0000 (09:40 -0400)]
robustly handle reconnect param across all pymysql variants
Fixed issue in aiomysql and asyncmy dialects that appears as of using
pymysql 1.2.0; the dialects were not properly taking into account logic
that detects the argument signature of pymysql's ``ping()`` method which
was added as part of :ticket:`10492`.
We add a "does ping have reconnect" check for all three DBAPIs
individually. To suit asyncmy's use of cython we also needed to
adjust vendored getargspec() routines.
Mike Bayer [Thu, 30 Apr 2026 19:13:39 +0000 (15:13 -0400)]
resolve table names using MetaData.schema default in declarative registry
Also resolved class-level MetaData not being consulted by the
declarative class registry when resolving string-based table
references. The registry now uses the same metadata resolution
logic as table creation, checking for a class-specific ``metadata``
attribute before falling back to ``registry.metadata``. The
``_metadata_for_cls`` function was factored into ``orm/util.py``
for shared use by both ``decl_base.py`` and ``clsregistry.py``.
David Lord [Sun, 17 May 2026 20:09:16 +0000 (16:09 -0400)]
document postgresql_nulls_not_distinct
<!-- Provide a general summary of your proposed changes in the Title field above -->
### Description
<!-- Describe your changes in detail -->
https://github.com/sqlalchemy/sqlalchemy/issues/8240 and https://github.com/sqlalchemy/sqlalchemy/pull/9834 added support for `NULLS NOT DISTINCT` to the PostgreSQL dialect, but didn't add it to the docs (only the change log). This adds a section to the "Constraint Options" section of the PostgreSQL dialect docs.
### Checklist
<!-- go over following points. check them with an `x` if they do apply, (they turn into clickable checkboxes once the PR is submitted, so no need to do everything at once)
-->
This pull request is:
- [x] A documentation / typographical / small typing error fix
- Good to go, no issue or tests are needed
- [ ] A short code fix
- please include the issue number, and create an issue if none exists, which
must include a complete example of the issue. one line code fixes without an
issue and demonstration will not be accepted.
- Please include: `Fixes: #<issue number>` in the commit message
- please include tests. one line code fixes without tests will not be accepted.
- [ ] A new feature implementation
- please include the issue number, and create an issue if none exists, which must
include a complete example of how the feature would look.
- Please include: `Fixes: #<issue number>` in the commit message
- please include tests.
Mike Bayer [Tue, 12 May 2026 18:30:37 +0000 (14:30 -0400)]
Block Result.unique() with Result.yield_per() for ORM results
The unique() + yield_per combination was only blocked when yield_per
was set via execution_options(yield_per=N); calling these as methods
on the result (e.g. result.unique().yield_per(N)) bypassed the check
and silently produced incorrect results.
Restructured _unique_filters on SimpleResultMetaData to be a callable
_create_unique_filters that receives the Result, allowing it to check
the yield_per state regardless of how it was activated.
populate existing can be in exec option in session.get
The ``populate_existing`` execution option is now honored when passed
in the :paramref:`.Session.get.execution_options` dict by the method
:meth:`.Session.get` and analogous in other session kinds. The current
:paramref:`.Session.get.populate_existing` parameter will takes precedence
if specified, overriding the value of the execution options.