Luckily the Python community is home to giants. sqlalchemy author is one such giant and he has something of interest to contribute to this conversation. But first i bore you with stuff we all already know.
from typing import AsyncIterator, Iterator
import contextlib
@contextlib.asynccontextmanager
async def somecoroutine() -> AsyncIterator[int]:
teardownfcn = lambda : None
something = 4
try:
yield something
finally:
teardownfcn()
@contextlib.contextmanager
def somefunction() -> Iterator[int]:
teardownfcn = lambda : None
something = 4
try:
yield something
finally:
teardownfcn()
async with somecoroutine() as stuff:
print(f"some int {stuff!s}"
with somefunction() as stuff:
print(f"some int {stuff!s}"
Although not a class, this pops out an AsyncIterator and shows sync equivalent.
From deep in the bowels of sqlalchemy
from sqlalchemy.ext.asyncio.base import GeneratorStartableContext, asyncstartablecontext
@asyncstartablecontext
async def somecoroutine() -> GeneratorStartableContext[int]:
teardownfcn = lambda : print("Cookie monster says, yum yum yum")
someiterable = [1,2,3]
try:
yield from someiterable
except GeneratorExit:
pass
else:
teardownfcn()
# no teardown
gen = await somecoroutine()
for thecount in gen:
print(f"The Count says, {thecount!s}")
# teardown occurs
async with gen in somecoroutine():
for thecount in gen:
print(f"The Count says, {thecount!s}")
Should print
The Count says, 1
The Count says, 2
The Count says, 3
The Count says, 1
The Count says, 2
The Count says, 3
Cookie monster says, yum yum yum
The decorated function can be called either as async with fn()
, or
await fn()
. This is decidedly different from what
@contextlib.asynccontextmanager
supports, and the usage pattern
is different as well.
Above, GeneratorExit
is caught if the function were used as an
await
. In this case, it's essential that the cleanup does not
occur, so there should not be a finally
block.
If GeneratorExit
is not invoked, this means we're in __aexit__
and we were invoked as a context manager, and cleanup should proceed.
So instead of a class with __anext__
and __aiter__
an asyncstartablecontext with yield from
could be a possible alternative.