]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
fix test suite warnings
authorMike Bayer <mike_mp@zzzcomputing.com>
Sun, 30 Apr 2023 22:27:24 +0000 (18:27 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 10 May 2023 00:23:48 +0000 (20:23 -0400)
fix a handful of warnings that were emitting but not raising,
usually because they were inside an "expect_warnings" block.

modify "expect_warnings" to always use "raise_on_any_unexpected"
behavior; remove this parameter.

Fixed issue in semi-private ``await_only()`` and ``await_fallback()``
concurrency functions where the given awaitable would remain un-awaited if
the function threw a ``GreenletError``, which could cause "was not awaited"
warnings later on if the program continued. In this case, the given
awaitable is now cancelled before the exception is thrown.

Change-Id: I33668c5e8c670454a3d879e559096fb873b57244

28 files changed:
doc/build/changelog/unreleased_20/await_cancel.rst [new file with mode: 0644]
doc/build/orm/queryguide/columns.rst
examples/versioned_history/test_versioning.py
lib/sqlalchemy/dialects/postgresql/asyncpg.py
lib/sqlalchemy/dialects/sqlite/aiosqlite.py
lib/sqlalchemy/orm/_orm_constructors.py
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/testing/assertions.py
lib/sqlalchemy/util/_concurrency_py3k.py
test/aaa_profiling/test_memusage.py
test/base/test_concurrency_py3k.py
test/dialect/mysql/test_query.py
test/engine/test_execute.py
test/engine/test_pool.py
test/ext/asyncio/test_session_py3k.py
test/ext/test_deprecations.py
test/orm/declarative/test_basic.py
test/orm/declarative/test_inheritance.py
test/orm/inheritance/test_relationship.py
test/orm/test_collection.py
test/orm/test_deprecations.py
test/orm/test_eager_relations.py
test/orm/test_joins.py
test/orm/test_relationships.py
test/orm/test_session.py
test/orm/test_utils.py
test/sql/test_delete.py
test/sql/test_insert_exec.py

diff --git a/doc/build/changelog/unreleased_20/await_cancel.rst b/doc/build/changelog/unreleased_20/await_cancel.rst
new file mode 100644 (file)
index 0000000..5d50a4f
--- /dev/null
@@ -0,0 +1,8 @@
+.. change::
+    :tags: bug, asyncio
+
+    Fixed issue in semi-private ``await_only()`` and ``await_fallback()``
+    concurrency functions where the given awaitable would remain un-awaited if
+    the function threw a ``GreenletError``, which could cause "was not awaited"
+    warnings later on if the program continued. In this case, the given
+    awaitable is now cancelled before the exception is thrown.
index 255c1f9028b26cbb20c63a648d5b70e84f23c4e4..93d0919ba56a15f220368991ff9f6f06d74079b1 100644 (file)
@@ -869,7 +869,7 @@ onto newly loaded instances of ``A``::
     >>> orm_stmt = (
     ...     select(User)
     ...     .from_statement(union_stmt)
-    ...     .options(with_expression(User.book_count, union_stmt.c.book_count))
+    ...     .options(with_expression(User.book_count, union_stmt.selected_columns.book_count))
     ... )
     >>> for user in session.scalars(orm_stmt):
     ...     print(f"Username: {user.name}  Number of books: {user.book_count}")
index 9caadc04332f2f122e3d8918fc84e7daf46ce5be..392a415ff468e90ee9d27a7719bb00ab5c6818ff 100644 (file)
@@ -13,9 +13,9 @@ from sqlalchemy import Integer
 from sqlalchemy import join
 from sqlalchemy import select
 from sqlalchemy import String
-from sqlalchemy.ext.declarative import declarative_base
 from sqlalchemy.orm import clear_mappers
 from sqlalchemy.orm import column_property
+from sqlalchemy.orm import declarative_base
 from sqlalchemy.orm import DeclarativeBase
 from sqlalchemy.orm import deferred
 from sqlalchemy.orm import exc as orm_exc
index c879205e4a18ae70670588a7f40ed699d059adb5..d198620d36a3e9921b805207ae037281e1577bb0 100644 (file)
@@ -850,6 +850,7 @@ class AsyncAdapt_asyncpg_connection(AdaptedConnection):
 
     def terminate(self):
         self._connection.terminate()
+        self._started = False
 
     @staticmethod
     def _default_name_func():
index f9c60efc17ea6b6561e8367965502588b7f5e2cf..2981976acc108033cb9da67bdf1c3a36ecca1cef 100644 (file)
@@ -239,6 +239,16 @@ class AsyncAdapt_aiosqlite_connection(AdaptedConnection):
     def close(self):
         try:
             self.await_(self._connection.close())
+        except ValueError:
+            # this is undocumented for aiosqlite, that ValueError
+            # was raised if .close() was called more than once, which is
+            # both not customary for DBAPI and is also not a DBAPI.Error
+            # exception. This is now fixed in aiosqlite via my PR
+            # https://github.com/omnilib/aiosqlite/pull/238, so we can be
+            # assured this will not become some other kind of exception,
+            # since it doesn't raise anymore.
+
+            pass
         except Exception as error:
             self._handle_exception(error)
 
index 563fef3c503be74bf0d71556a437d9a95ca0cefe..fab93f68242647c36b4e47b3121f077ead0f16b4 100644 (file)
@@ -83,6 +83,7 @@ _T = typing.TypeVar("_T")
     "The :class:`.AliasOption` object is not necessary "
     "for entities to be matched up to a query that is established "
     "via :meth:`.Query.from_statement` and now does nothing.",
+    enable_warnings=False,  # AliasOption itself warns
 )
 def contains_alias(alias: Union[Alias, Subquery]) -> AliasOption:
     r"""Return a :class:`.MapperOption` that will indicate to the
index 5cd7cc11704c27074d274d9d39ba3db82ec74ebc..e6381bee16b553f8b170bf3442026696d86f6df7 100644 (file)
@@ -1436,7 +1436,13 @@ class Query(
         to the given list of columns
 
         """
+        return self._values_no_warn(*columns)
 
+    _values = values
+
+    def _values_no_warn(
+        self, *columns: _ColumnsClauseArgument[Any]
+    ) -> Iterable[Any]:
         if not columns:
             return iter(())
         q = self._clone().enable_eagerloads(False)
@@ -1445,8 +1451,6 @@ class Query(
             q.load_options += {"_yield_per": 10}
         return iter(q)  # type: ignore
 
-    _values = values
-
     @util.deprecated(
         "1.4",
         ":meth:`_query.Query.value` "
@@ -1460,7 +1464,7 @@ class Query(
 
         """
         try:
-            return next(self.values(column))[0]  # type: ignore
+            return next(self._values_no_warn(column))[0]  # type: ignore
         except StopIteration:
             return None
 
@@ -3332,7 +3336,7 @@ class AliasOption(interfaces.LoaderOption):
 
     @util.deprecated(
         "1.4",
-        "The :class:`.AliasOption` is not necessary "
+        "The :class:`.AliasOption` object is not necessary "
         "for entities to be matched up to a query that is established "
         "via :meth:`.Query.from_statement` and now does nothing.",
     )
index a51d831a90f080028c93ce23a55659bc5abf986f..e7b4161672c11a2c646d9c00632f01a3fa5ec782 100644 (file)
@@ -47,7 +47,7 @@ def expect_warnings(*messages, **kw):
     Note that the test suite sets SAWarning warnings to raise exceptions.
 
     """  # noqa
-    return _expect_warnings(sa_exc.SAWarning, messages, **kw)
+    return _expect_warnings_sqla_only(sa_exc.SAWarning, messages, **kw)
 
 
 @contextlib.contextmanager
@@ -84,11 +84,15 @@ def emits_warning(*messages):
 
 
 def expect_deprecated(*messages, **kw):
-    return _expect_warnings(sa_exc.SADeprecationWarning, messages, **kw)
+    return _expect_warnings_sqla_only(
+        sa_exc.SADeprecationWarning, messages, **kw
+    )
 
 
 def expect_deprecated_20(*messages, **kw):
-    return _expect_warnings(sa_exc.Base20DeprecationWarning, messages, **kw)
+    return _expect_warnings_sqla_only(
+        sa_exc.Base20DeprecationWarning, messages, **kw
+    )
 
 
 def emits_warning_on(db, *messages):
@@ -140,6 +144,29 @@ _SEEN = None
 _EXC_CLS = None
 
 
+def _expect_warnings_sqla_only(
+    exc_cls,
+    messages,
+    regex=True,
+    search_msg=False,
+    assert_=True,
+):
+    """SQLAlchemy internal use only _expect_warnings().
+
+    Alembic is using _expect_warnings() directly, and should be updated
+    to use this new interface.
+
+    """
+    return _expect_warnings(
+        exc_cls,
+        messages,
+        regex=regex,
+        search_msg=search_msg,
+        assert_=assert_,
+        raise_on_any_unexpected=True,
+    )
+
+
 @contextlib.contextmanager
 def _expect_warnings(
     exc_cls,
@@ -150,7 +177,6 @@ def _expect_warnings(
     raise_on_any_unexpected=False,
     squelch_other_warnings=False,
 ):
-
     global _FILTERS, _SEEN, _EXC_CLS
 
     if regex or search_msg:
@@ -181,7 +207,6 @@ def _expect_warnings(
             real_warn = warnings.warn
 
         def our_warn(msg, *arg, **kw):
-
             if isinstance(msg, _EXC_CLS):
                 exception = type(msg)
                 msg = str(msg)
@@ -379,7 +404,7 @@ def assert_warns(except_cls, callable_, *args, **kwargs):
 
 
     """
-    with _expect_warnings(except_cls, [".*"], squelch_other_warnings=True):
+    with _expect_warnings_sqla_only(except_cls, [".*"]):
         return callable_(*args, **kwargs)
 
 
@@ -394,12 +419,11 @@ def assert_warns_message(except_cls, msg, callable_, *args, **kwargs):
     rather than regex.match().
 
     """
-    with _expect_warnings(
+    with _expect_warnings_sqla_only(
         except_cls,
         [msg],
         search_msg=True,
         regex=False,
-        squelch_other_warnings=True,
     ):
         return callable_(*args, **kwargs)
 
@@ -413,7 +437,6 @@ def assert_raises_message_context_ok(
 def _assert_raises(
     except_cls, callable_, args, kwargs, msg=None, check_context=False
 ):
-
     with _expect_raises(except_cls, msg, check_context) as ec:
         callable_(*args, **kwargs)
     return ec.error
@@ -892,7 +915,6 @@ class AssertsExecutionResults:
         return result
 
     def assert_sql(self, db, callable_, rules):
-
         newrules = []
         for rule in rules:
             if isinstance(rule, dict):
index b29cc2119eb3b779c8dc047d313f4b4a8cff824a..0e26425b250b0c83e19ce56154cf80c24c00d126 100644 (file)
@@ -17,11 +17,13 @@ from typing import Awaitable
 from typing import Callable
 from typing import Coroutine
 from typing import Optional
+from typing import TYPE_CHECKING
 from typing import TypeVar
 
 from .langhelpers import memoized_property
 from .. import exc
 from ..util.typing import Protocol
+from ..util.typing import TypeGuard
 
 _T = TypeVar("_T")
 
@@ -78,6 +80,26 @@ class _AsyncIoGreenlet(greenlet):  # type: ignore
             self.gr_context = driver.gr_context
 
 
+_T_co = TypeVar("_T_co", covariant=True)
+
+if TYPE_CHECKING:
+
+    def iscoroutine(
+        awaitable: Awaitable[_T_co],
+    ) -> TypeGuard[Coroutine[Any, Any, _T_co]]:
+        ...
+
+else:
+    iscoroutine = asyncio.iscoroutine
+
+
+def _safe_cancel_awaitable(awaitable: Awaitable[Any]) -> None:
+    # https://docs.python.org/3/reference/datamodel.html#coroutine.close
+
+    if iscoroutine(awaitable):
+        awaitable.close()
+
+
 def await_only(awaitable: Awaitable[_T]) -> _T:
     """Awaits an async function in a sync method.
 
@@ -90,6 +112,8 @@ def await_only(awaitable: Awaitable[_T]) -> _T:
     # this is called in the context greenlet while running fn
     current = getcurrent()
     if not isinstance(current, _AsyncIoGreenlet):
+        _safe_cancel_awaitable(awaitable)
+
         raise exc.MissingGreenlet(
             "greenlet_spawn has not been called; can't call await_only() "
             "here. Was IO attempted in an unexpected place?"
@@ -117,6 +141,9 @@ def await_fallback(awaitable: Awaitable[_T]) -> _T:
     if not isinstance(current, _AsyncIoGreenlet):
         loop = get_event_loop()
         if loop.is_running():
+
+            _safe_cancel_awaitable(awaitable)
+
             raise exc.MissingGreenlet(
                 "greenlet_spawn has not been called and asyncio event "
                 "loop is already running; can't call await_fallback() here. "
index 9b2cb31befa7abc8ec004c42d859e0bdd14668a3..dc5a39910e6e269784d71235018f7230e3cbb326 100644 (file)
@@ -296,7 +296,6 @@ class MemUsageTest(EnsureZeroed):
 
     @testing.requires.cextensions
     def test_cycles_in_row(self):
-
         tup = result.result_tuple(["a", "b", "c"])
 
         @profile_memory()
@@ -695,7 +694,6 @@ class MemUsageWBackendTest(fixtures.MappedTest, EnsureZeroed):
         @testing.emits_warning()
         @profile_memory()
         def go():
-
             # execute with a non-unicode object. a warning is emitted,
             # this warning shouldn't clog up memory.
 
@@ -1066,7 +1064,9 @@ class MemUsageWBackendTest(fixtures.MappedTest, EnsureZeroed):
 
         t1_mapper = self.mapper_registry.map_imperatively(T1, t1)
 
-        @testing.emits_warning()
+        @testing.emits_warning(
+            r"This declarative base", r"Property .* being replaced"
+        )
         @profile_memory()
         def go():
             class T2:
@@ -1128,7 +1128,9 @@ class MemUsageWBackendTest(fixtures.MappedTest, EnsureZeroed):
             s = table2.select()
             sess = session()
             with testing.expect_deprecated(
-                "Implicit coercion of SELECT and " "textual SELECT constructs"
+                "Implicit coercion of SELECT and textual SELECT constructs",
+                "An alias is being generated automatically",
+                assert_=False,
             ):
                 sess.query(Foo).join(s, Foo.bars).all()
             sess.rollback()
@@ -1637,7 +1639,6 @@ class CycleTest(_fixtures.FixtureTest):
 
     @testing.provide_metadata
     def test_optimized_get(self):
-
         Base = declarative_base(metadata=self.metadata)
 
         class Employee(Base):
index 6a3098a6a36705bd4bac91d02a6bfe21143d7964..17a0fafb5a43aab86335cdd18c6824b9c78ffb18 100644 (file)
@@ -95,7 +95,12 @@ class TestAsyncioCompat(fixtures.TestBase):
         ):
             await_only(to_await)
 
-        # ensure no warning
+        # existing awaitable is done
+        with expect_raises(RuntimeError):
+            await greenlet_spawn(await_fallback, to_await)
+
+        # no warning for a new one...
+        to_await = run1()
         await greenlet_spawn(await_fallback, to_await)
 
     @async_test
@@ -118,7 +123,8 @@ class TestAsyncioCompat(fixtures.TestBase):
         ):
             await greenlet_spawn(go)
 
-        await to_await
+        with expect_raises(RuntimeError):
+            await to_await
 
     @async_test
     async def test_await_only_error(self):
@@ -141,7 +147,8 @@ class TestAsyncioCompat(fixtures.TestBase):
         ):
             await greenlet_spawn(go)
 
-        await to_await
+        with expect_raises(RuntimeError):
+            await to_await
 
     @async_test
     async def test_contextvars(self):
index 0ce3611826b5f855989cb76b2449e50f6135d15a..921b5c52baa752f4d730197c36ae671ed1164f0b 100644 (file)
@@ -11,9 +11,9 @@ from sqlalchemy import or_
 from sqlalchemy import select
 from sqlalchemy import String
 from sqlalchemy import Table
-from sqlalchemy import testing
 from sqlalchemy import true
 from sqlalchemy.testing import eq_
+from sqlalchemy.testing import expect_warnings
 from sqlalchemy.testing import fixtures
 from sqlalchemy.testing import is_
 
@@ -22,23 +22,26 @@ class IdiosyncrasyTest(fixtures.TestBase):
     __only_on__ = "mysql", "mariadb"
     __backend__ = True
 
-    @testing.emits_warning()
     def test_is_boolean_symbols_despite_no_native(self, connection):
+        with expect_warnings("Datatype BOOL does not support CAST"):
+            is_(
+                connection.scalar(select(cast(true().is_(true()), Boolean))),
+                True,
+            )
 
-        is_(
-            connection.scalar(select(cast(true().is_(true()), Boolean))),
-            True,
-        )
-
-        is_(
-            connection.scalar(select(cast(true().is_not(true()), Boolean))),
-            False,
-        )
+        with expect_warnings("Datatype BOOL does not support CAST"):
+            is_(
+                connection.scalar(
+                    select(cast(true().is_not(true()), Boolean))
+                ),
+                False,
+            )
 
-        is_(
-            connection.scalar(select(cast(false().is_(false()), Boolean))),
-            True,
-        )
+        with expect_warnings("Datatype BOOL does not support CAST"):
+            is_(
+                connection.scalar(select(cast(false().is_(false()), Boolean))),
+                True,
+            )
 
 
 class MatchTest(fixtures.TablesTest):
index e99448a26bd5488b0c8a5044fe7bd779f9e765a9..2c5f091b47acbee50739c0a91e79f999635b07dd 100644 (file)
@@ -2044,6 +2044,7 @@ class EngineEventsTest(fixtures.TestBase):
                 select(1).compile(dialect=e1.dialect), (), {}
             )
 
+    @testing.emits_warning("The garbage collector is trying to clean up")
     def test_execute_events(self):
 
         stmts = []
@@ -2283,6 +2284,7 @@ class EngineEventsTest(fixtures.TestBase):
 
         eng_copy = copy.copy(eng)
         eng_copy.dispose(close=close)
+
         copy_conn = eng_copy.connect()
         dbapi_conn_two = copy_conn.connection.dbapi_connection
 
@@ -2294,6 +2296,9 @@ class EngineEventsTest(fixtures.TestBase):
         else:
             is_(dbapi_conn_one, conn.connection.dbapi_connection)
 
+        conn.close()
+        copy_conn.close()
+
     def test_retval_flag(self):
         canary = []
 
@@ -3702,8 +3707,8 @@ class DialectEventTest(fixtures.TestBase):
 
         conn.connection.invalidate(soft=True)
         conn.close()
-        conn = e.connect()
-        eq_(conn.info["boom"], "one")
+        with e.connect() as conn:
+            eq_(conn.info["boom"], "one")
 
     def test_connect_do_connect_info_there_after_invalidate(self):
         # test that info is maintained after the do_connect()
@@ -3720,8 +3725,9 @@ class DialectEventTest(fixtures.TestBase):
         eq_(conn.info["boom"], "one")
 
         conn.connection.invalidate()
-        conn = e.connect()
-        eq_(conn.info["boom"], "one")
+
+        with e.connect() as conn:
+            eq_(conn.info["boom"], "one")
 
 
 class SetInputSizesTest(fixtures.TablesTest):
index 6730d7012baa2b74115d356e148a99ca525ea4fe..cca6e258928725b79b5ab3dfdf66a47867971e26 100644 (file)
@@ -1705,6 +1705,7 @@ class QueuePoolTest(PoolTestBase):
         )
 
     @testing.combinations((True,), (False,))
+    @testing.emits_warning("The garbage collector")
     def test_userspace_disconnectionerror_weakref_finalizer(self, detach_gced):
         dbapi, pool = self._queuepool_dbapi_fixture(
             pool_size=1, max_overflow=2, _is_asyncio=detach_gced
@@ -1737,6 +1738,7 @@ class QueuePoolTest(PoolTestBase):
 
         not_closed_dbapi_conn = conn.dbapi_connection
         del conn
+
         gc_collect()
 
         if detach_gced:
@@ -1744,6 +1746,9 @@ class QueuePoolTest(PoolTestBase):
             eq_(not_closed_dbapi_conn.mock_calls, [])
         else:
             # new connection reset and returned to pool
+            # this creates a gc-level warning that is not easy to pin down,
+            # hence we use the testing.emits_warning() decorator just to squash
+            # it
             eq_(not_closed_dbapi_conn.mock_calls, [call.rollback()])
 
     @testing.requires.timing_intensive
index 36135a43db3759d286f3f977049fac28f044e2c4..fed59995f65044273b9331894b4043e7b23a106a 100644 (file)
@@ -741,6 +741,7 @@ class AsyncORMBehaviorsTest(AsyncFixture):
                 (exc.StatementError, exc.MissingGreenlet)
             ):
                 a1.b = b2
+
         else:
             a1.b = b2
 
index 97c4172ba7be4686a11f34e80c8e2d14500b7250..653a0215799a31fcb6b074ad1019f99431a560e9 100644 (file)
@@ -70,11 +70,11 @@ class HorizontalShardTest(fixtures.TestBase):
         m1 = mock.Mock()
 
         with testing.expect_deprecated(
-            "The ``query_chooser`` parameter is deprecated; please use"
+            "The ``query_chooser`` parameter is deprecated; please use",
         ):
             s = ShardedSession(
                 shard_chooser=m1.shard_chooser,
-                id_chooser=m1.id_chooser,
+                identity_chooser=m1.identity_chooser,
                 query_chooser=m1.query_chooser,
             )
 
index d0e56819cbb91e18f8dec9cdbb48a7f8cfb5b328..6dcecc0c1110c117fa8f6173c1beb9462739b40e 100644 (file)
@@ -1394,7 +1394,6 @@ class DeclarativeMultiBaseTest(
             r"non-schema SQLAlchemy expression object; ",
             r"Attribute 'y' on class <class .*Foo5.* appears to be a "
             r"non-schema SQLAlchemy expression object; ",
-            raise_on_any_unexpected=True,
         ):
 
             class Foo5(Base):
index 333d2423071428083abd51779e7dd4e74c565765..7d66c0d0b252ea40a171493465d3e2706428d74a 100644 (file)
@@ -1026,7 +1026,6 @@ class DeclarativeInheritanceTest(
         if not combine_on_b and not omit_from_statements:
             ctx = expect_warnings(
                 "Implicitly combining column a.extra with column b.extra",
-                raise_on_any_unexpected=True,
             )
         else:
             ctx = contextlib.nullcontext()
index 6d168d2cee8f795539b2eabd3e9fe23d0f0106a0..ab9c29918ba9ff007f458aa3aeb295bfe4473d81 100644 (file)
@@ -61,7 +61,6 @@ def _aliased_join_warning(arg):
     return testing.expect_warnings(
         r"An alias is being generated automatically against joined entity "
         r"Mapper\[%s\] due to overlapping tables" % (arg,),
-        raise_on_any_unexpected=True,
     )
 
 
index 650b30aaa1c74a53550f892c6ab4d8f96612085b..85efbe0a37bbd5a30f905900328e17d4daffbec5 100644 (file)
@@ -2928,7 +2928,6 @@ class AttrKeyedDictKeysTest(fixtures.TestBase):
                 "'bs' was None;",
                 "Attribute keyed dictionary value for attribute "
                 "'B.a' was None;",
-                raise_on_any_unexpected=True,
             ):
                 a1 = A(bs={"k1": b1, "k2": b2})
         sess.add(a1)
@@ -2943,7 +2942,6 @@ class AttrKeyedDictKeysTest(fixtures.TestBase):
             with expect_warnings(
                 "Attribute keyed dictionary value for attribute "
                 "'unknown relationship' was None;",
-                raise_on_any_unexpected=True,
             ):
                 eq_(a1.bs, {None: b2})
 
@@ -3074,7 +3072,6 @@ class UnpopulatedAttrTest(fixtures.TestBase):
             with expect_warnings(
                 "Attribute keyed dictionary value for attribute "
                 "'B.a' was None;",
-                raise_on_any_unexpected=True,
             ):
                 b1.a = a1
         else:
index a3e2f4ef720b54001638a40176dfdae1a7337b55..db64f91f185790250aae03b3b44b5c3816e5e8e8 100644 (file)
@@ -414,7 +414,6 @@ class MiscDeprecationsTest(fixtures.TestBase):
         with expect_deprecated(
             rf"The column_property.{paramname} parameter is deprecated "
             r"for column_property\(\)",
-            raise_on_any_unexpected=True,
         ):
             column_property(column("q"), **{paramname: value})
 
@@ -429,7 +428,6 @@ class MiscDeprecationsTest(fixtures.TestBase):
             r"for column_property\(\)",
             r"The column_property.kw_only parameter is deprecated "
             r"for column_property\(\)",
-            raise_on_any_unexpected=True,
         ):
 
             class MyClass(dc_decl_base):
@@ -488,7 +486,8 @@ class DeprecatedQueryTest(_fixtures.FixtureTest, AssertsCompiledSQL):
         s = addresses.select()
         sess = fixture_session()
         with testing.expect_deprecated(
-            "Implicit coercion of SELECT and " "textual SELECT constructs"
+            "Implicit coercion of SELECT and textual SELECT constructs",
+            "An alias is being generated automatically against joined entity",
         ):
             self.assert_compile(
                 sess.query(User).join(s, User.addresses),
@@ -1592,7 +1591,7 @@ class InstancesTest(QueryTest, AssertsCompiledSQL):
         def go():
             with testing.expect_deprecated(
                 "The AliasOption object is not necessary for entities to be "
-                "matched up to a query"
+                "matched up to a query",
             ):
                 result = (
                     q.options(
@@ -1624,7 +1623,8 @@ class InstancesTest(QueryTest, AssertsCompiledSQL):
 
         def go():
             with testing.expect_deprecated(
-                r"Using the Query.instances\(\) method without a context"
+                r"The Query.instances\(\) method is deprecated",
+                r"Using the Query.instances\(\) method without a context",
             ):
                 result = list(
                     q.options(contains_eager(User.addresses)).instances(
@@ -1639,7 +1639,8 @@ class InstancesTest(QueryTest, AssertsCompiledSQL):
 
         def go():
             with testing.expect_deprecated(
-                r"Using the Query.instances\(\) method without a context"
+                r"The Query.instances\(\) method is deprecated",
+                r"Using the Query.instances\(\) method without a context",
             ):
                 result = list(
                     q.options(contains_eager(User.addresses)).instances(
@@ -1674,7 +1675,6 @@ class InstancesTest(QueryTest, AssertsCompiledSQL):
                 r"Using the Query.instances\(\) method without a context",
                 r"The Query.instances\(\) method is deprecated and will be "
                 r"removed in a future release.",
-                raise_on_any_unexpected=True,
             ):
                 result = list(
                     q.options(
@@ -1721,7 +1721,6 @@ class InstancesTest(QueryTest, AssertsCompiledSQL):
                 r"Using the Query.instances\(\) method without a context",
                 r"The Query.instances\(\) method is deprecated and will be "
                 r"removed in a future release.",
-                raise_on_any_unexpected=True,
             ):
                 result = list(
                     q.options(
index ef6d4b684f7bc3100144c807e8bb0592e71d0a2b..fa44dbf10dcd6da0871dca8bba7663c9df57ac4c 100644 (file)
@@ -1656,7 +1656,7 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
         def go():
             eq_(u1.addresses[0].user, u1)
 
-        with testing.expect_warnings(raise_on_any_unexpected=True):
+        with testing.expect_warnings():
             self.assert_sql_execution(
                 testing.db,
                 go,
@@ -1707,7 +1707,7 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
         def go():
             eq_(u1.addresses[0].user, u1)
 
-        with testing.expect_warnings(raise_on_any_unexpected=True):
+        with testing.expect_warnings():
             self.assert_sql_execution(
                 testing.db,
                 go,
index 3120a160dacdfa3870c5f59ae9e0e3c57cd9b3d4..ff684d846bed062983e8f9d398bd3e01ba04a761 100644 (file)
@@ -170,7 +170,6 @@ class InheritedJoinTest(InheritedTest, AssertsCompiledSQL):
             r"Mapper\[Manager\(managers\)\] due to overlapping",
             "An alias is being generated automatically against joined entity "
             r"Mapper\[Boss\(boss\)\] due to overlapping",
-            raise_on_any_unexpected=True,
         ):
             self.assert_compile(
                 q,
index 0cbcc01f3532048c5458dbd9a29ee5bd3a7d2f4f..12651fe36432c169ce3a81322bc6d44d44d79c23 100644 (file)
@@ -33,7 +33,6 @@ from sqlalchemy.orm.interfaces import MANYTOONE
 from sqlalchemy.orm.interfaces import ONETOMANY
 from sqlalchemy.testing import assert_raises
 from sqlalchemy.testing import assert_raises_message
-from sqlalchemy.testing import assert_warns_message
 from sqlalchemy.testing import AssertsCompiledSQL
 from sqlalchemy.testing import eq_
 from sqlalchemy.testing import expect_raises_message
@@ -656,18 +655,15 @@ class OverlappingFksSiblingTest(fixtures.MappedTest):
         add_bsub2_a_viewonly=False,
         add_b_a_overlaps=None,
     ):
-
         Base = self.mapper_registry.generate_base()
 
         class A(Base):
-
             __tablename__ = "a"
 
             id = Column(Integer, primary_key=True)
             a_members = relationship("AMember", backref="a")
 
         class AMember(Base):
-
             __tablename__ = "a_member"
 
             a_id = Column(Integer, ForeignKey("a.id"), primary_key=True)
@@ -707,14 +703,12 @@ class OverlappingFksSiblingTest(fixtures.MappedTest):
         # however, *no* warning should be emitted otherwise.
 
         class BSub1(B):
-
             if add_bsub1_a:
                 a = relationship("A")
 
             __mapper_args__ = {"polymorphic_identity": "bsub1"}
 
         class BSub2(B):
-
             if add_bsub2_a_viewonly:
                 a = relationship("A", viewonly=True)
 
@@ -729,7 +723,6 @@ class OverlappingFksSiblingTest(fixtures.MappedTest):
         return A, AMember, B, BSub1, BSub2
 
     def _fixture_two(self, setup_backrefs=False, setup_overlaps=False):
-
         Base = self.mapper_registry.generate_base()
 
         # purposely using the comma to make sure parsing the comma works
@@ -874,15 +867,13 @@ class OverlappingFksSiblingTest(fixtures.MappedTest):
 
     @testing.provide_metadata
     def test_simple_warn(self):
-        assert_warns_message(
-            exc.SAWarning,
+        with expect_warnings(
             r"relationship '(?:Child.parent|Parent.children)' will copy "
             r"column parent.id to column child.parent_id, which conflicts "
             r"with relationship\(s\): '(?:Parent.children|Child.parent)' "
-            r"\(copies parent.id to child.parent_id\).",
-            self._fixture_two,
-            setup_backrefs=False,
-        )
+            r"\(copies parent.id to child.parent_id\)."
+        ):
+            self._fixture_two(setup_backrefs=False)
 
     @testing.combinations((True,), (False,), argnames="set_overlaps")
     def test_fixture_five(self, metadata, set_overlaps):
@@ -965,15 +956,12 @@ class OverlappingFksSiblingTest(fixtures.MappedTest):
 
     @testing.provide_metadata
     def test_double_rel_same_mapper_warns(self):
-        assert_warns_message(
-            exc.SAWarning,
+        with expect_warnings(
             r"relationship 'Parent.child[12]' will copy column parent.id to "
             r"column child.parent_id, which conflicts with relationship\(s\): "
-            r"'Parent.child[12]' \(copies parent.id to child.parent_id\)",
-            self._fixture_three,
-            use_same_mappers=True,
-            setup_overlaps=False,
-        )
+            r"'Parent.child[12]' \(copies parent.id to child.parent_id\)"
+        ):
+            self._fixture_three(use_same_mappers=True, setup_overlaps=False)
 
     @testing.provide_metadata
     def test_double_rel_same_mapper_overlaps_works(self):
@@ -985,48 +973,37 @@ class OverlappingFksSiblingTest(fixtures.MappedTest):
 
     @testing.provide_metadata
     def test_warn_one(self):
-        assert_warns_message(
-            exc.SAWarning,
+        with expect_warnings(
             r"relationship '(?:BSub1.a|BSub2.a_member|B.a)' will copy column "
-            r"(?:a.id|a_member.a_id) to column b.a_id",
-            self._fixture_one,
-            add_b_a=True,
-            add_bsub1_a=True,
-        )
+            r"(?:a.id|a_member.a_id) to column b.a_id"
+        ):
+            self._fixture_one(add_b_a=True, add_bsub1_a=True)
 
     @testing.provide_metadata
     def test_warn_two(self):
-        assert_warns_message(
-            exc.SAWarning,
+        with expect_warnings(
             r"relationship '(?:BSub1.a|B.a_member)' will copy column "
-            r"(?:a.id|a_member.a_id) to column b.a_id",
-            self._fixture_one,
-            add_b_amember=True,
-            add_bsub1_a=True,
-        )
+            r"(?:a.id|a_member.a_id) to column b.a_id"
+        ):
+            self._fixture_one(add_b_amember=True, add_bsub1_a=True)
 
     @testing.provide_metadata
     def test_warn_three(self):
-        assert_warns_message(
-            exc.SAWarning,
-            r"relationship '(?:BSub1.a|B.a_member|B.a)' will copy column "
-            r"(?:a.id|a_member.a_id) to column b.a_id",
-            self._fixture_one,
-            add_b_amember=True,
-            add_bsub1_a=True,
-            add_b_a=True,
-        )
+        with expect_warnings(
+            r"relationship '(?:BSub1.a|B.a_member|BSub2.a_member|B.a)' "
+            r"will copy column (?:a.id|a_member.a_id) to column b.a_id",
+        ):
+            self._fixture_one(
+                add_b_amember=True, add_bsub1_a=True, add_b_a=True
+            )
 
     @testing.provide_metadata
     def test_warn_four(self):
-        assert_warns_message(
-            exc.SAWarning,
+        with expect_warnings(
             r"relationship '(?:B.a|BSub2.a_member|B.a)' will copy column "
-            r"(?:a.id|a_member.a_id) to column b.a_id",
-            self._fixture_one,
-            add_bsub2_a_viewonly=True,
-            add_b_a=True,
-        )
+            r"(?:a.id|a_member.a_id) to column b.a_id"
+        ):
+            self._fixture_one(add_bsub2_a_viewonly=True, add_b_a=True)
 
     @testing.provide_metadata
     def test_works_one(self):
@@ -1303,12 +1280,11 @@ class CompositeSelfRefFKTest(fixtures.MappedTest, AssertsCompiledSQL):
             },
         )
 
-        assert_warns_message(
-            exc.SAWarning,
+        with expect_warnings(
             r"relationship .* will copy column .* to column "
-            r"employee_t.company_id, which conflicts with relationship\(s\)",
-            configure_mappers,
-        )
+            r"employee_t.company_id, which conflicts with relationship\(s\)"
+        ):
+            configure_mappers()
 
     def test_annotated_no_overwriting(self):
         Employee, Company, employee_t, company_t = (
@@ -2537,7 +2513,6 @@ class JoinConditionErrorTest(fixtures.TestBase):
         argnames="argname, arg",
     )
     def test_invalid_string_args(self, registry, argname, arg):
-
         kw = {argname: arg}
         Base = registry.generate_base()
 
@@ -4789,7 +4764,6 @@ class SecondaryNestedJoinTest(
         )
 
     def test_render_lazyload(self):
-
         A = self.classes.A
         sess = fixture_session()
         a1 = sess.query(A).filter(A.name == "a1").first()
@@ -5968,7 +5942,6 @@ class InactiveHistoryNoRaiseTest(_fixtures.FixtureTest):
         delete,
         legacy_inactive_history_style,
     ):
-
         if delete:
             assert not backref, "delete and backref are mutually exclusive"
 
index 5a0431788dd060276162bf74eb08b4a224d13c64..395603a771533282478fb13e8fc38a6eb6988b04 100644 (file)
@@ -36,6 +36,7 @@ from sqlalchemy.testing import config
 from sqlalchemy.testing import engines
 from sqlalchemy.testing import eq_
 from sqlalchemy.testing import expect_raises_message
+from sqlalchemy.testing import expect_warnings
 from sqlalchemy.testing import fixtures
 from sqlalchemy.testing import is_
 from sqlalchemy.testing import is_false
@@ -108,7 +109,6 @@ class ExecutionTest(_fixtures.FixtureTest):
         )
 
     def test_no_string_execute(self, connection):
-
         with Session(bind=connection) as sess:
             with expect_raises_message(
                 sa.exc.ArgumentError,
@@ -242,7 +242,6 @@ class TransScopingTest(_fixtures.FixtureTest):
         self.mapper_registry.map_imperatively(Address, addresses)
 
         with Session(testing.db) as sess:
-
             sess.add(User(name="u1"))
             sess.commit()
 
@@ -2293,13 +2292,13 @@ class FlushWarningsTest(fixtures.MappedTest):
         def evt(mapper, conn, instance):
             instance.addresses.append(Address(email="x1"))
 
-        self._test(evt, "collection append")
+        self._test(evt, "collection append", "related attribute set")
 
     def test_o2m_cascade_remove(self):
         def evt(mapper, conn, instance):
             del instance.addresses[0]
 
-        self._test(evt, "collection remove")
+        self._test(evt, "collection remove", "related attribute set")
 
     def test_m2o_cascade_add(self):
         User = self.classes.User
@@ -2308,14 +2307,19 @@ class FlushWarningsTest(fixtures.MappedTest):
             instance.addresses[0].user = User(name="u2")
 
         with expect_raises_message(orm_exc.FlushError, ".*Over 100"):
-            self._test(evt, "related attribute set")
+            self._test(
+                evt,
+                "related attribute set",
+                "collection remove",
+                "collection append",
+            )
 
     def test_m2o_cascade_remove(self):
         def evt(mapper, conn, instance):
             a1 = instance.addresses[0]
             del a1.user
 
-        self._test(evt, "related attribute delete")
+        self._test(evt, "related attribute delete", "collection remove")
 
     def test_plain_add(self):
         Address = self.classes.Address
@@ -2344,7 +2348,7 @@ class FlushWarningsTest(fixtures.MappedTest):
         ):
             self._test(evt, r"Session.delete\(\)")
 
-    def _test(self, fn, method):
+    def _test(self, fn, *methods):
         User = self.classes.User
         Address = self.classes.Address
 
@@ -2353,6 +2357,8 @@ class FlushWarningsTest(fixtures.MappedTest):
 
         u1 = User(name="u1", addresses=[Address(name="a1")])
         s.add(u1)
-        assert_warns_message(
-            sa.exc.SAWarning, "Usage of the '%s'" % method, s.commit
-        )
+
+        with expect_warnings(
+            *[f"Usage of the '{method}'" for method in methods]
+        ):
+            s.commit()
index 3bd0230dea67482f9993b49c7268d93fc9870b91..4d6c148639f759240b726b9ce473568fec6020b6 100644 (file)
@@ -78,7 +78,6 @@ class ContextualWarningsTest(fixtures.TestBase):
                 "bar.foo_id, which conflicts with relationship(s): 'Foo.bars' "
                 "(copies foo.id to bar.foo_id). "
             ),
-            raise_on_any_unexpected=True,
         ):
             decl_base.registry.configure()
 
@@ -96,7 +95,6 @@ class ContextualWarningsTest(fixtures.TestBase):
                 "invoked automatically in response to a user-initiated "
                 "operation.)"
             ),
-            raise_on_any_unexpected=True,
         ):
             FooAlias = aliased(Foo)
             assert hasattr(FooAlias, "bars")
@@ -115,7 +113,6 @@ class ContextualWarningsTest(fixtures.TestBase):
                 "invoked automatically in response to a user-initiated "
                 "operation.)"
             ),
-            raise_on_any_unexpected=True,
         ):
             foo = Foo()
             assert hasattr(foo, "bars")
@@ -145,7 +142,6 @@ class ContextualWarningsTest(fixtures.TestBase):
                 "process, which was invoked automatically in response to a "
                 "user-initiated operation.)"
             ),
-            raise_on_any_unexpected=True,
         ):
             sess.execute(select(Foo))
 
index 5b7e5ebbe3a9ab082521b24bae8c781f9279ade8..9f9e104b6a16ff79d39e86f2a8f67e2920bf68e1 100644 (file)
@@ -76,11 +76,15 @@ class DeleteTest(_DeleteTestBase, fixtures.TablesTest, AssertsCompiledSQL):
     def test_where_empty(self):
         table1 = self.tables.mytable
 
-        with expect_deprecated():
+        with expect_deprecated(
+            r"Invoking and_\(\) without arguments is deprecated"
+        ):
             self.assert_compile(
                 table1.delete().where(and_()), "DELETE FROM mytable"
             )
-        with expect_deprecated():
+        with expect_deprecated(
+            r"Invoking or_\(\) without arguments is deprecated"
+        ):
             self.assert_compile(
                 table1.delete().where(or_()), "DELETE FROM mytable"
             )
index f545671e714235f5fb9d68b77c4ba6a5abff38ee..0fee9bd35a2609f25a11e98c4182b2d2b96734e0 100644 (file)
@@ -1239,7 +1239,6 @@ class IMVSentinelTest(fixtures.TestBase):
             ):
                 return expect_warnings(
                     "Batches were downgraded",
-                    raise_on_any_unexpected=True,
                 )
 
         return contextlib.nullcontext()
@@ -1982,7 +1981,6 @@ class IMVSentinelTest(fixtures.TestBase):
         ):
             with expect_warnings(
                 "Batches were downgraded for sorted INSERT",
-                raise_on_any_unexpected=True,
             ):
                 result = connection.execute(stmt, data)
         else:
@@ -2407,7 +2405,6 @@ class IMVSentinelTest(fixtures.TestBase):
 
         with expect_warnings(
             r".*but has no Python-side or server-side default ",
-            raise_on_any_unexpected=True,
         ):
             with expect_raises(exc.IntegrityError):
                 connection.execute(