我确实了解,sqlalchemy.orm.scoping.scoped_session使用session_factory创建会话,并且还具有注册表以通过__call__()调用返回已经存在的会话。
但是也可以直接在.query上调用scoped_session方法,这完全使我感到困惑,因为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的工作很简单;为所有要求的人举行会议。作为产生对该会话的更透明访问的一种方式,scoped_session还包括代理行为,这意味着注册表本身可以直接像Session一样被对待。在此对象上调用方法时,它们将代理到注册表维护的基础会话:


Session = scoped_session(some_factory)

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



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


因此,这种行为是正常的,但是如何实现呢? (通常不是代理,但在此示例中正是)

谢谢。

最佳答案

显然,您已经很好地了解了sqlalchemy.orm.scoping.scoped_session类,如果您在同一模块中稍稍向下看,则会发现以下代码段(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",
        ...,
    )


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

setattr(scoped_session, meth, instrument(meth))


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

由于for meth in Session.public_methods:循环是在模块的全局命名空间中定义的,因此在其他任何机会都不能使用scoped_session的情况下,它将在编译时执行。因此,当您的代码可以保留scoped_session实例时,这些方法已经被猴子修补了。

关于python - 如何实现sqlalchemy.orm.scoping.scoped_session代理功能?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/59972523/

10-12 04:02
查看更多