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.