【问题标题】:Why does a yielded SQLAlchemy Session in a FastAPI dependency close once it goes out of scope?为什么 FastAPI 依赖项中产生的 SQLAlchemy 会话一旦超出范围就会关闭?
【发布时间】:2021-12-16 11:19:43
【问题描述】:

FastAPI docs 中建议使用生成器函数设置 SQLAlchemy 数据库会话依赖项,如下所示:

async def get_db():
    db = DBSession()
    try:
        yield db
    finally:
        db.close()

我的问题是为什么 finally 块会被执行?我的印象是生成器函数在每次产量时都会暂停执行。一旦会话对象超出范围,get_db 的执行不应该被丢弃,db.close() 从未运行过吗?

【问题讨论】:

    标签: python sqlalchemy fastapi


    【解决方案1】:

    如果您在 try 之前将 breakpoint() 放入依赖项中并使用 w 选项查看调用树,您可以看到生成器类型函数 next()contextlib 模块代码中被调用:

    [0]   c:\users\homeuser\.pyenv\pyenv-win\versions\3.7.9\lib\threading.py(890)_bootstrap()
    -> self._bootstrap_inner()
    [1]   c:\users\homeuser\.pyenv\pyenv-win\versions\3.7.9\lib\threading.py(926)_bootstrap_inner()
    -> self.run()
    [2]   c:\users\homeuser\.pyenv\pyenv-win\versions\3.7.9\lib\threading.py(870)run()
    -> self._target(*self._args, **self._kwargs)
    [3]   c:\users\homeuser\.pyenv\pyenv-win\versions\3.7.9\lib\concurrent\futures\thread.py(80)_worker()
    -> work_item.run()
    [4]   c:\users\homeuser\.pyenv\pyenv-win\versions\3.7.9\lib\concurrent\futures\thread.py(57)run()
    -> result = self.fn(*self.args, **self.kwargs)
    [5]   c:\users\homeuser\.pyenv\pyenv-win\versions\3.7.9\lib\contextlib.py(112)__enter__()
    -> return next(self.gen)
    

    请注意,这个next() 调用是在__enter__ 特殊函数内完成的,上下文管理器在使用with 关键字打开它时会使用该函数。现在,如果继续执行yield 语句并等到您的断点将进入finally 块,您可以再次使用w 查看调用树:

    [0]   c:\users\homeuser\.pyenv\pyenv-win\versions\3.7.9\lib\threading.py(890)_bootstrap()
    -> self._bootstrap_inner()
    [1]   c:\users\homeuser\.pyenv\pyenv-win\versions\3.7.9\lib\threading.py(926)_bootstrap_inner()
    -> self.run()
    [2]   c:\users\homeuser\.pyenv\pyenv-win\versions\3.7.9\lib\threading.py(870)run()
    -> self._target(*self._args, **self._kwargs)
    [3]   c:\users\homeuser\.pyenv\pyenv-win\versions\3.7.9\lib\concurrent\futures\thread.py(80)_worker()
    -> work_item.run()
    [4]   c:\users\homeuser\.pyenv\pyenv-win\versions\3.7.9\lib\concurrent\futures\thread.py(57)run()
    -> result = self.fn(*self.args, **self.kwargs)
    [5]   c:\users\homeuser\.pyenv\pyenv-win\versions\3.7.9\lib\contextlib.py(119)__exit__()
    -> next(self.gen)
    

    现在您会注意到,在退出上下文管理器对象时,您的生成器函数上调用了 next()

    暂停是对的,但功能状态也会被记住。因此,当__enter__ 中的第一个next() 被调用时,它会生成您的会话对象,然后第二个next() 调用会恢复函数执行,从而可以进入finally 块内。

    我认为@contextmanager 装饰器在 FastAPI 实现中的某个地方使用,它处理依赖关系,因此可以在Depends 中使用生成器。 查看this 文章,该文章很好地解释了 Python 生成器的工作原理。

    【讨论】:

    • 感谢您的回答。我认为 FastAPI 依赖项的幕后工作并不明显。我假设他们有某种自定义上下文管理器实现,在进入和退出时运行next()。我认为finally 会运行还有另一个原因,更适用于生成器。我将为此发布我自己的答案。
    【解决方案2】:

    devaerial 发布了一个很好的答案,关于在由 FastAPI 生成器依赖项实现的上下文管理器的进入和退出魔术方法期间如何调用 next()。但是,我想在此处发布finally 块执行的另一个更普遍的原因。

    我找到了this 更广泛的关于生成器的答案。假设我们有一个生成器函数...

    def gen():
        yield 1
        print("test")
    

    我的输出会是这样的:

    >>> print(next(gen()))
    1
    

    但是,如果我使用 try...finally 修改我的函数

    def gen():
        try:
            yield 1
        finally:
            print("test")
    

    我会得到这个输出:

    >>> print(next(gen()))
    test
    1
    

    一旦生成器被销毁,finally 块保证在垃圾回收之前执行。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-13
      • 2020-05-16
      • 2014-12-16
      相关资源
      最近更新 更多