diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2023-04-30 18:27:24 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2023-05-09 20:23:48 -0400 |
commit | 60b31198311eedfa3814e7098c94d3aa29338fdd (patch) | |
tree | 5fe3a55ef67ab14fa63a0a3122a1326b830ceb72 /lib/sqlalchemy | |
parent | 228490ead7048f2e558c25b0f055bdb952272ec4 (diff) | |
download | sqlalchemy-60b31198311eedfa3814e7098c94d3aa29338fdd.tar.gz |
fix test suite warnings
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
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r-- | lib/sqlalchemy/dialects/postgresql/asyncpg.py | 1 | ||||
-rw-r--r-- | lib/sqlalchemy/dialects/sqlite/aiosqlite.py | 10 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/_orm_constructors.py | 1 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/query.py | 12 | ||||
-rw-r--r-- | lib/sqlalchemy/testing/assertions.py | 42 | ||||
-rw-r--r-- | lib/sqlalchemy/util/_concurrency_py3k.py | 27 |
6 files changed, 79 insertions, 14 deletions
diff --git a/lib/sqlalchemy/dialects/postgresql/asyncpg.py b/lib/sqlalchemy/dialects/postgresql/asyncpg.py index c879205e4..d198620d3 100644 --- a/lib/sqlalchemy/dialects/postgresql/asyncpg.py +++ b/lib/sqlalchemy/dialects/postgresql/asyncpg.py @@ -850,6 +850,7 @@ class AsyncAdapt_asyncpg_connection(AdaptedConnection): def terminate(self): self._connection.terminate() + self._started = False @staticmethod def _default_name_func(): diff --git a/lib/sqlalchemy/dialects/sqlite/aiosqlite.py b/lib/sqlalchemy/dialects/sqlite/aiosqlite.py index f9c60efc1..2981976ac 100644 --- a/lib/sqlalchemy/dialects/sqlite/aiosqlite.py +++ b/lib/sqlalchemy/dialects/sqlite/aiosqlite.py @@ -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) diff --git a/lib/sqlalchemy/orm/_orm_constructors.py b/lib/sqlalchemy/orm/_orm_constructors.py index 563fef3c5..fab93f682 100644 --- a/lib/sqlalchemy/orm/_orm_constructors.py +++ b/lib/sqlalchemy/orm/_orm_constructors.py @@ -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 diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 5cd7cc117..e6381bee1 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -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.", ) diff --git a/lib/sqlalchemy/testing/assertions.py b/lib/sqlalchemy/testing/assertions.py index a51d831a9..e7b416167 100644 --- a/lib/sqlalchemy/testing/assertions.py +++ b/lib/sqlalchemy/testing/assertions.py @@ -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): diff --git a/lib/sqlalchemy/util/_concurrency_py3k.py b/lib/sqlalchemy/util/_concurrency_py3k.py index b29cc2119..0e26425b2 100644 --- a/lib/sqlalchemy/util/_concurrency_py3k.py +++ b/lib/sqlalchemy/util/_concurrency_py3k.py @@ -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. " |