【问题标题】:How is sqlalchemy.orm.scoping.scoped_session proxying ability implemented?sqlalchemy.orm.scoping.scoped_session 代理能力是如何实现的?
【发布时间】:2020-05-15 06:31:15
【问题描述】:

我明白,sqlalchemy.orm.scoping.scoped_session 使用session_factory 创建会话,并且还拥有一个注册表以通过__call__() 调用返回已经存在的会话。 但是也可以在scoped_session 上直接调用.query 方法,这完全让我感到困惑,因为scoped_session: 1.没有这个方法 2. 不是sqlalchemy.orm.session.Session 的动态包装器并且 3. 不是sqlalchemy.orm.session.Session的子类。

scoped_session 如何发送查询?我只是看不到任何允许这样做的间接或抽象......但它确实有效。

from sqlalchemy.orm import sessionmaker,scoped_session
from sqlalchemy import create_engine

user, password, server, dbname = "123","123","123", "123"
s = 'oracle://%s:%s@%s/%s' % (user, password, server, dbname)
some_engine = create_engine(s)
_sessionmaker = sessionmaker(bind=some_engine)
sc_sess = scoped_session(_sessionmaker) # here sc_sess is an isntance of "sqlalchemy.orm.scoping.scoped_session"
sc_sess.query(...) # works! but why?

# the following is what i expect to work and to be normal workflow
session = sc_sess() # returns an instance of sqlalchemy.orm.session.Session
session.query(...)

SqlAlchemy Documentation 中描述了此行为:

隐式方法访问

scoped_session 的工作很简单;为所有要求的人举行会议。作为对这个 Session 产生更透明访问的一种手段,scoped_session 还包括代理行为,这意味着注册中心本身可以直接像 Session 一样对待;当在这个对象上调用方法时,它们被代理到由注册表维护的底层会话:

Session = scoped_session(some_factory)

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

上面的代码完成了与获取当前会话相同的任务,方法是调用注册表,然后使用该会话。

所以这种行为是正常的,但它是如何实现的呢? (不是一般的代理,而是在这个例子中)

谢谢。

【问题讨论】:

    标签: python session proxy sqlalchemy


    【解决方案1】:

    您显然已经很好地了解了 sqlalchemy.orm.scoping.scoped_session 类,如果您在同一个模块中再往下看一点,您会发现以下 sn-p (link):

    def instrument(name):
        def do(self, *args, **kwargs):
            return getattr(self.registry(), name)(*args, **kwargs)
    
        return do
    
    
    for meth in Session.public_methods:
        setattr(scoped_session, meth, instrument(meth))
    

    如果我们从下往上剖析,我们首先得到for meth in Session.public_methods: 循环,其中Session.public_methods 只是Session 公开的方法名称的元组,而字符串"query" 是其中之一:

    class Session(_SessionClassMethods):
        ...
        public_methods = (
            ...,
            "query",
            ...,
        )
    

    Session.public_methods 中的每个名称 (meth) 都传递给循环内的 setattr 调用:

    setattr(scoped_session, meth, instrument(meth))
    

    分配给scoped_session 上的方法名称的值是对instrument(meth) 的调用的返回值,这是一个称为do() 的闭包。该函数调用scoped_session.registry 以获取注册的Session 对象,获取命名方法(meth),并使用传递给do()*args**kwargs 调用它。

    由于for meth in Session.public_methods: 循环是在模块的全局命名空间中定义的,它在编译时执行,在其他任何东西有机会使用scoped_session 之前。因此,当您的代码可以获取 scoped_session 实例时,这些方法已经在那里进行了猴子补丁。

    【讨论】:

    • 不敢相信我已经监督了它! :D 非常感谢你,SuperShoot。
    猜你喜欢
    • 2021-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-23
    • 2022-11-02
    相关资源
    最近更新 更多