Reputation: 118
I do understand, that a sqlalchemy.orm.scoping.scoped_session
uses a session_factory
to create a session and also possesses a registry to return an already present session through the __call__()
call.
But one can also directly call the .query
method upon scoped_session
and that completely confuses me, since scoped_session:
1. does not have this method
2. is not a dynamic wrapper of a sqlalchemy.orm.session.Session
and
3. is not a subclass of sqlalchemy.orm.session.Session
.
How is scoped_session
able to dispatch a query? I just don't see any indirection or abstraction that would allow for this.. yet it works.
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(...)
This behaviour is described in the SqlAlchemy Documentation:
Implicit Method Access
The job of the scoped_session is simple; hold onto a Session for all who ask for it. As a means of producing more transparent access to this Session, the scoped_session also includes proxy behavior, meaning that the registry itself can be treated just like a Session directly; when methods are called on this object, they are proxied to the underlying Session being maintained by the registry:
Session = scoped_session(some_factory)
# equivalent to:
#
# session = Session()
# print(session.query(MyClass).all())
#
print(Session.query(MyClass).all())
The above code accomplishes the same task as that of acquiring the current Session by calling upon the registry, then using that Session.
So this behaviour is normal, but how is it implemented? (not proxy in general, but precisely in this example)
Thanks.
Upvotes: 1
Views: 832
Reputation: 10872
You've obviously had a good look at the sqlalchemy.orm.scoping.scoped_session
class, and if you look just a little further down in the same module, you'll find the following snippet (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))
If we dissect that from the bottom up, we've first got the for meth in Session.public_methods:
loop, where Session.public_methods
is simply a tuple of the names of methods that a Session
exposes, and the string "query"
is one of those:
class Session(_SessionClassMethods):
...
public_methods = (
...,
"query",
...,
)
Each of those names (meth
) in Session.public_methods
is passed to the setattr
call inside the loop:
setattr(scoped_session, meth, instrument(meth))
The value that is assigned to the name of the method on the scoped_session
is the return value of the call to instrument(meth)
, which is a closure called, do()
. That function calls the scoped_session.registry
to get the registered Session
object, gets the named method (meth
), and calls it with the *args
& **kwargs
that were passed to do()
.
As the for meth in Session.public_methods:
loop is defined in the global namespace of the module, it is executed at compile time, before anything else has a chance to use the scoped_session
. So by the time your code can get a hold of a scoped_session
instance, those methods have already been monkey patched on there.
Upvotes: 2