我有这个功能:
async_session = contextvars.ContextVar("async_session")
async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
async with async_session_maker() as session:
try:
_token = async_session.set(session)
yield session
finally:
async_session.reset(_token)
失败并显示:
ValueError: <Token var=<ContextVar name='async_session' at 0x7e1470e00e00> at 0x7e14706d4680> was created in a different Context
怎么会发生这种事? AFAICT 更改
Context
的唯一方法是整个函数调用。 那么,在 yield
期间,上下文如何发生变化?
此函数被用作 FastAPI
Depends
,以防产生影响 - 但我看不出它是如何工作的。 它在 Python 3.8 下运行,FastAPI 的版本同样古老 - 0.54。
如果生成器用作常规生成器,在我们控制的
for
循环中,真的很难思考 contextvars 的上下文如何改变。但框架可以做自己的事情:它可以将生成器存储在变量中,并从任意上下文调用其 __next__
方法。
实际上,ContextVars 甚至不打算与异步生成器一起工作 - 检查PEP 567的全文 - 它被重写,简化了PEP 550的原始提案,因为暂停帧和运行外帧的迭代代码变得太复杂,并且该功能被简单地删除了。
我有一个包可以使上下文变量的使用更简单 - “extracontext” - 并且我在那里支持异步生成器中的上下文变量。
无论哪种方式,我的建议是恢复“threading.local”命名空间,并且我确实向“contextvars”添加了“上下文管理器”功能。
你可以
pip install python-extracontext
。除了文档字符串以及 README 中嵌入的内容之外,我没有写太多文档 https://github.com/jsbueno/extracontext
有了这个软件包,您应该能够使用:
from extracontext import ContextLocal
# async_session = contextvars.ContextVar("async_session")
async_session_ns = ContextLocal()
async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
async with (async_session_maker() as session, async_session_ns):
with async_session_ns:
async_session_ns.session = session
# any code wanting this, should be able to use just
# "async_session_ns.session" in any expression
yield session
# no finally blocks needed, as ContextLocal
# works as a context manager.
如果由于任何原因它对您不起作用,请告诉我。