I have a problem where I need to check that something's occurred in the log file between two points in execution.
Currently I do this:
print("start")
# do something here
print("end")
res = await check_log(...) # check in the log if something's happened
# between start and end and return the line if so
I'm wondering if I can instead have a contextlib asynccontextmanager that looks like this:
class foo():
def __init__(self, ...):
...
@asynccontextmanager
async def bar(self):
print("start")
fut = asyncio.Future()
yield fut
print("end")
res = await check_log(...)
fut.set_result(res)
and which I call like this, where the class contains variables I would otherwise have to pass to check_log:
obj = foo(...)
async with obj.bar() as b:
# do something here
res = b.result()
Is there anything fundamentally unsafe or wrong about this? If so, is there a better way of doing this? I know with a regular context manager you can get around it by setting an attribute though I'm not sure it's possible with contextlib.
This looks perfectly valid and ok.
The fundamental thing to keep in mind is that execution will only proceed in the code containing the with block after the __aexit__ method in the context manager is executed - which means running the part past the yield fut in your method, and therefore, the completion of the the await check_log(...) execution.
If you want check_log to perform concurrently with the block after the with block, that is equally as easy to do by changing creating check_log as a task, and setting its done callback to a function that will set fut's result - then, in this example, b could be awaited whenever one would like to check that result. Otherwise, it is good as is. (Just, please, put that yield statement and the block after it in a try-finally compound).
Of course, note that if someone tries to await b inside the with block, your code will be dead-locked.
If you have some value that the code post- with can get with no side-effects, you could use a contextvar.ContextVars as a class attribute of foo:
import contextvars
class foo():
last_log = contextvars.ContextVar("last_log")
def __init__(self, ...):
...
@asynccontextmanager
async def bar(self):
print("start")
try:
yield None
finally:
print("end")
self.last_log.set(await check_log(...))
...
obj = foo(...)
async with obj.bar():
# do something here
res = obj.last_log.get()