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()