上下文/线程本地会话¶

从本节召回 我什么时候做一个 Session ,什么时候提交,什么时候关闭? 介绍了“会话范围”的概念,重点介绍了Web应用程序和链接 Session 一个Web请求。大多数现代Web框架都包含集成工具,因此 Session 可以自动管理,这些工具应该在可用时使用。

sqlAlchemy包含自己的helper对象,这有助于建立用户定义的 Session 范围。第三方集成系统也使用它来帮助构建其集成方案。

对象是 scoped_session 对象,它表示 登记处 属于 Session 对象。如果您不熟悉注册表模式,可以在 Patterns of Enterprise Architecture .

注解

这个 scoped_session 对象是许多SQLAlchemy应用程序使用的一个非常流行和有用的对象。但是,重要的是要注意它 只有一种方法 关于问题 Session 管理。如果您不熟悉SQLAlchemy,尤其是当您对术语“线程局部变量”感到陌生时,我们建议您在可能的情况下首先熟悉现成的集成系统,例如 Flask-SQLAlchemyzope.sqlalchemy .

A scoped_session 通过调用它,传递它 factory 可以创建新的 Session 对象。工厂只是在调用时生成新对象的东西,并且在 Session 最常见的工厂是 sessionmaker ,在本节前面介绍。下面我们将说明这种用法:

>>> from sqlalchemy.orm import scoped_session
>>> from sqlalchemy.orm import sessionmaker

>>> session_factory = sessionmaker(bind=some_engine)
>>> Session = scoped_session(session_factory)

这个 scoped_session 我们创建的对象现在将调用 sessionmaker 当我们“调用”注册表时:

>>> some_session = Session()

上面, some_session 是的实例 Session ,我们现在可以使用它与数据库进行对话。同样 Session 也存在于 scoped_session 我们创建的注册表。如果我们第二次去登记处,我们会把 same Session ::

>>> some_other_session = Session()
>>> some_session is some_other_session
True

此模式允许应用程序的不同部分调用全局 scoped_session 以便所有这些区域可以共享同一个会话,而无需显式传递它。这个 Session 我们已经在我们的注册表中建立了,在我们通过调用 scoped_session.remove() ::

>>> Session.remove()

这个 scoped_session.remove() 方法第一次调用 Session.close() 关于电流 Session ,其作用是释放 Session 首先,然后丢弃 Session 本身。”这里的“释放”意味着连接将返回到其连接池,并且任何事务状态都将回滚,最终使用 rollback() 基本DBAPI连接的方法。

在这一点上, scoped_session 对象为“空”,将创建一个 new Session 再次调用时。如下图所示,这与 Session 我们以前有过:

>>> new_session = Session()
>>> new_session is some_session
False

上面的一系列步骤简单地说明了“注册表”模式的概念。有了这个基本概念,我们可以讨论这个模式如何进行的一些细节。

隐式方法访问

的工作 scoped_session 很简单;抓住 Session 为所有要求它的人。作为实现更透明访问的一种手段 Session , the scoped_session 还包括 代理行为 也就是说,注册表本身可以像 Session 直接;当在此对象上调用方法时,它们是 代理的 到基础 Session 由登记处维护:

Session = scoped_session(some_factory)

# equivalent to:
#
# session = Session()
# print(session.query(MyClass).all())
#
print(Session.query(MyClass).all())

上述代码完成的任务与获取当前 Session 通过调用注册表,然后使用 Session .

线程本地作用域

熟悉多线程编程的用户会注意到,将任何内容表示为全局变量通常是一个坏主意,因为这意味着许多线程将同时访问全局对象。这个 Session 对象完全设计为在 non-concurrent 在多线程方面,fashion意味着“一次只在一个线程中”。所以我们上面的例子 scoped_session 用法,其中相同 Session 对象是在多个调用之间维护的,这意味着某些进程需要就位,这样多个线程之间的多个调用实际上不会得到同一会话的句柄。我们称之为这个概念 线程本地存储 也就是说,使用一个特殊的对象,它将为每个应用程序线程维护一个不同的对象。python通过 threading.local() 构建。这个 scoped_session 对象默认使用此对象作为存储,以便 Session 为所有需要 scoped_session 注册表,但仅在单个线程的范围内。调用另一线程中的注册表的调用方 Session 其他线程的本地实例。

使用这种技术, scoped_session 提供一种快速且相对简单的(如果熟悉线程本地存储)方法,在应用程序中提供一个可安全从多个线程调用的全局对象。

这个 scoped_session.remove() 方法和往常一样,删除当前 Session 与线程关联(如果有)。然而,一个优势是 threading.local() 对象是,如果应用程序线程本身结束,那么该线程的“存储”也将被垃圾收集。因此,在不需要调用 scoped_session.remove() . 但是,交易本身的范围,即通过 Session.commit()Session.rollback() ,通常仍然是必须在适当的时间显式安排的内容,除非应用程序实际将线程的寿命与事务的寿命绑定在一起。

将线程本地作用域与Web应用程序一起使用

如本节所述 我什么时候做一个 Session ,什么时候提交,什么时候关闭? Web应用程序是围绕 网络请求 以及将此类应用程序与 Session 通常意味着 Session 将与该请求关联。事实证明,大多数python web框架,除了异步框架twisted和tornado等明显的例外,都以一种简单的方式使用线程,以便在单个框架的范围内接收、处理和完成特定的web请求。 工作者线程 . 当请求结束时,工作线程被释放到一个工作线程池中,在该池中可以处理另一个请求。

Web请求和线程的这种简单对应意味着 Session 使用线程意味着它也与在该线程内运行的Web请求关联,反之亦然,前提是 Session 仅在Web请求开始后创建,并在Web请求结束前删除。所以使用 scoped_session 作为一种快速整合 Session 使用Web应用程序。下面的序列图说明了这个流程:

Web Server          Web Framework        SQLAlchemy ORM Code
--------------      --------------       ------------------------------
startup        ->   Web framework        # Session registry is established
                    initializes          Session = scoped_session(sessionmaker())

incoming
web request    ->   web request     ->   # The registry is *optionally*
                    starts               # called upon explicitly to create
                                         # a Session local to the thread and/or request
                                         Session()

                                         # the Session registry can otherwise
                                         # be used at any time, creating the
                                         # request-local Session() if not present,
                                         # or returning the existing one
                                         Session.query(MyClass) # ...

                                         Session.add(some_object) # ...

                                         # if data was modified, commit the
                                         # transaction
                                         Session.commit()

                    web request ends  -> # the registry is instructed to
                                         # remove the Session
                                         Session.remove()

                    sends output      <-
outgoing web    <-
response

利用上述流程,整合 Session 对于Web应用程序,有两个要求:

  1. 创建单个 scoped_session 当Web应用程序首次启动时注册,确保应用程序的其余部分可以访问此对象。

  2. 确保 scoped_session.remove() 当Web请求结束时调用,通常通过与Web框架的事件系统集成来建立“请求结束”事件。

如前所述,上述模式是 只有一种可能的方法 整合一个 Session 在Web框架中,一个特别重要的假设是 Web框架将Web请求与应用程序线程关联 . 然而它是 强烈建议使用Web框架本身提供的集成工具(如果可用) ,而不是 scoped_session .

特别是,虽然使用本地线程比较方便,但是 Session 关联 直接请求 而不是当前线程。关于自定义范围的下一节详细介绍了一个更高级的配置,它可以结合使用 scoped_session 直接基于请求的作用域,或任何类型的作用域。

使用自定义创建的作用域

这个 scoped_session 对象的“线程本地”作用域的默认行为只是关于如何“作用域”的许多选项之一 Session . 自定义范围可以基于任何现有的获取“当前正在使用的对象”的系统来定义。

假设一个Web框架定义了一个库函数 get_current_request() . 使用此框架构建的应用程序可以随时调用此函数,结果将是 Request 表示当前正在处理的请求的对象。如果 Request 对象是可哈希的,那么这个函数可以很容易地与 scoped_sessionSession 请求。下面我们结合Web框架提供的假设事件标记来说明这一点。 on_request_end ,允许在请求结束时调用代码::

from my_web_framework import get_current_request, on_request_end
from sqlalchemy.orm import scoped_session, sessionmaker

Session = scoped_session(sessionmaker(bind=some_engine), scopefunc=get_current_request)

@on_request_end
def remove_session(req):
    Session.remove()

上面,我们举例说明 scoped_session 以通常的方式,除了我们将请求返回函数传递为“scopefunc”。这个指令 scoped_session 每当调用注册表以返回当前 Session . 在这种情况下,特别重要的是,我们要确保实现可靠的“删除”系统,因为本词典不是自行管理的。

上下文会话API

class sqlalchemy.orm.scoping.scoped_session(session_factory, scopefunc=None)

提供范围管理 Session 对象。

上下文/线程本地会话 教程。

__call__(**kw)

返回电流 Session ,使用 scoped_session.session_factory 如果不存在。

参数

**kw -- 关键字参数将传递给 scoped_session.session_factory 可调用,如果存在 Session 不存在。如果 Session 存在并且已传递关键字参数, InvalidRequestError 提高了。

__init__(session_factory, scopefunc=None)

构建新的 scoped_session .

参数
  • session_factory -- 创建新工厂 Session 实例。这通常是,但不一定是 sessionmaker .

  • scopefunc -- 定义当前作用域的可选函数。如果未通过,则 scoped_session 对象假定“线程本地”范围,并将使用python threading.local() 为了保持电流 Session . 如果传递,函数应返回一个哈希标记;此标记将用作字典中的键,以便存储和检索当前 Session .

configure(**kwargs)

重新配置 sessionmaker 用于此 scoped_session .

sessionmaker.configure() .

query_property(query_cls=None)

返回生成 Query 对象与类和当前 Session 当被召唤时。

例如。::

Session = scoped_session(sessionmaker())

class MyClass(object):
    query = Session.query_property()

# after mappers are defined
result = MyClass.query.filter(MyClass.name=='foo').all()

默认情况下生成会话配置的查询类的实例。要重写和使用自定义实现,请提供 query_cls 可赎回的。调用将使用类的映射器作为位置参数和会话关键字参数来调用。

对放置在类上的查询属性的数量没有限制。

remove()

处理电流 Session ,如果存在的话。

这是第一个调用 Session.close() 当前的方法 Session ,释放仍保留的任何现有事务/连接资源;事务将被回滚。这个 Session 然后被丢弃。在同一范围内的下一次使用时, scoped_session 会产生新的 Session 对象。

session_factory = None

这个 session_factory provided to _ _ Init_uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu当新的非作用域 :class:.Session` 或 Connection 需要数据库。

class sqlalchemy.util.ScopedRegistry(createfunc, scopefunc)

一种注册表,它可以在“作用域”函数的基础上存储单个类的一个或多个实例。

对象实现 __call__ 作为“getter”,所以调用给 myregistry() 将为当前作用域返回包含的对象。

参数
  • createfunc -- 返回要放置在注册表中的新对象的可调用文件

  • scopefunc -- 将返回一个键以存储/检索对象的可调用文件。

__init__(createfunc, scopefunc)

构建新的 ScopedRegistry .

参数
  • createfunc -- 一个创建函数,如果当前作用域不存在,它将为当前作用域生成一个新值。

  • scopefunc -- 返回表示当前作用域(如当前线程标识符)的哈希标记的函数。

clear()

清除当前范围(如果有)。

has()

如果对象存在于当前作用域中,则返回true。

set(obj)

设置当前作用域的值。

class sqlalchemy.util.ThreadLocalRegistry(createfunc)

基地: sqlalchemy.util._collections.ScopedRegistry

A ScopedRegistry 使用A threading.local() 用于存储的变量。