diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-10-25 09:10:09 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-11-03 18:42:52 -0400 |
commit | b96321ae79a0366c33ca739e6e67aaf5f4420db4 (patch) | |
tree | d56cb4cdf58e0b060f1ceb14f468eef21de0688b /lib/sqlalchemy/util | |
parent | 9bae9a931a460ff70172858ff90bcc1defae8e20 (diff) | |
download | sqlalchemy-b96321ae79a0366c33ca739e6e67aaf5f4420db4.tar.gz |
Support result.close() for all iterator patterns
This change contains new features for 2.0 only as well as some
behaviors that will be backported to 1.4.
For 1.4 and 2.0:
Fixed issue where the underlying DBAPI cursor would not be closed when
using :class:`_orm.Query` with :meth:`_orm.Query.yield_per` and direct
iteration, if a user-defined exception case were raised within the
iteration process, interrupting the iterator. This would lead to the usual
MySQL-related issues with server side cursors out of sync.
For 1.4 only:
A similar scenario can occur when using :term:`2.x` executions with direct
use of :class:`.Result`, in that case the end-user code has access to the
:class:`.Result` itself and should call :meth:`.Result.close` directly.
Version 2.0 will feature context-manager calling patterns to address this
use case. However within the 1.4 scope, ensured that ``.close()`` methods
are available on all :class:`.Result` implementations including
:class:`.ScalarResult`, :class:`.MappingResult`.
For 2.0 only:
To better support the use case of iterating :class:`.Result` and
:class:`.AsyncResult` objects where user-defined exceptions may interrupt
the iteration, both objects as well as variants such as
:class:`.ScalarResult`, :class:`.MappingResult`,
:class:`.AsyncScalarResult`, :class:`.AsyncMappingResult` now support
context manager usage, where the result will be closed at the end of
iteration.
Corrected various typing issues within the engine and async engine
packages.
Fixes: #8710
Change-Id: I3166328bfd3900957eb33cbf1061d0495c9df670
Diffstat (limited to 'lib/sqlalchemy/util')
-rw-r--r-- | lib/sqlalchemy/util/__init__.py | 1 | ||||
-rw-r--r-- | lib/sqlalchemy/util/compat.py | 23 |
2 files changed, 24 insertions, 0 deletions
diff --git a/lib/sqlalchemy/util/__init__.py b/lib/sqlalchemy/util/__init__.py index cd7e0fd81..4952cb501 100644 --- a/lib/sqlalchemy/util/__init__.py +++ b/lib/sqlalchemy/util/__init__.py @@ -46,6 +46,7 @@ from ._collections import UniqueAppender as UniqueAppender from ._collections import update_copy as update_copy from ._collections import WeakPopulateDict as WeakPopulateDict from ._collections import WeakSequence as WeakSequence +from .compat import anext_ as anext_ from .compat import arm as arm from .compat import b as b from .compat import b64decode as b64decode diff --git a/lib/sqlalchemy/util/compat.py b/lib/sqlalchemy/util/compat.py index 4ce1e7ff3..cda5ab6c1 100644 --- a/lib/sqlalchemy/util/compat.py +++ b/lib/sqlalchemy/util/compat.py @@ -111,6 +111,29 @@ else: return a +if py310: + anext_ = anext +else: + + _NOT_PROVIDED = object() + from collections.abc import AsyncIterator + + async def anext_(async_iterator, default=_NOT_PROVIDED): + """vendored from https://github.com/python/cpython/pull/8895""" + + if not isinstance(async_iterator, AsyncIterator): + raise TypeError( + f"anext expected an AsyncIterator, got {type(async_iterator)}" + ) + anxt = type(async_iterator).__anext__ + try: + return await anxt(async_iterator) + except StopAsyncIteration: + if default is _NOT_PROVIDED: + raise + return default + + def importlib_metadata_get(group): ep = importlib_metadata.entry_points() if not typing.TYPE_CHECKING and hasattr(ep, "select"): |