Hi !
While using Flask-SQLAlchemy extension I found that the builtin Model
base class had a weird behavior when using the .query attribute
It seems, looking at the implementation that it needs to access the
flask request context (for no good reason?), making it impossible to
use the query property outside of a request. It looks like this:
-----------------------------------------
class _QueryProperty(object):
def __init__(self, sa):
self.sa = sa
def __get__(self, obj, type):
try:
mapper = orm.class_mapper(type)
ctx = _request_ctx_stack.top
if mapper and ctx is not None:
return type.query_class(mapper, session=self.sa.session())
except UnmappedClassError:
return None
---------------------------------------
(Note how the _request_ctx_stack is accessed)
Instead of setting up the query property like it is now, I found that
the following workaround seems to work wonderfully:
db = SQLAlchemy()
db.Model.query = db.session.query_property(lambda mapper, **kwargs:
mapper.class_.query_class(mapper, **kwargs))
However i'm not sure if this is entirely OK, or if there are some
other issues I'm not aware of ?
Raf
Hi, On 2010-10-14 11:44 PM, Raphael Slinckx wrote: > It seems, looking at the implementation that it needs to access the > flask request context (for no good reason?), making it impossible to > use the query property outside of a request. It looks like this: It needs the request because from the request it knows the active application. Models can be used with more than one instance at the same time. You can use test_request_context() to work with the database from outside of a real HTTP request: app.test_request_context().push() Regards, Armin
> It needs the request because from the request it knows the active > application. Models can be used with more than one instance at the > same time. I'm trying to wrap my head around this, but I can't find any code in Flask-SQLAlchemy where the Query property actually depends on the application object. Also, wouldn't it be possible to make the query property available outside of a request context if Flask-SQLAlchemy is initialized directly with an app object, rather than using init_app(). I.e. kind of like the SQLAlchemy.engine @property currently behaves. Michael
Hi, On 2010-10-15 6:01 PM, Michael Elsdörfer wrote: > Also, wouldn't it be possible to make the query property available > outside of a request context if Flask-SQLAlchemy is initialized directly > with an app object, rather than using init_app(). You can initialize more than one app. The active app is taken from the bound request context. Regards, Armin
> You can initialize more than one app. The active app is taken from > the bound request context. I've checked the code again, and I honestly don't see the problem. It seems like a thread-local Session object is created on-demand when accessed, and it's bound to either sql_alchemy_instance.app, or the top app on the stack, with the former taking precedence. See in class _SignallingSession: self.app = db.app or _request_ctx_stack.top.app The same is true for the engine to which the session is bound (sql_alchemy_instance.engine also prefers self.app over the current app). In other words, I can do: db.session.add(Model()) db.session.commit() outside of a request. Why do I suddenly need a request if I subsequently want to do: Model.query.all() In fact, if I change the implementation of _QueryProperty.__get__ so that a request is no longer required, it works as expected, and the tests still pass (except for the one test that specifically asserts the old behavior of the .query property). Michael
> In other words, I can do: > > db.session.add(Model()) > db.session.commit() > > outside of a request. Why do I suddenly need a request if I subsequently > want to do: > > Model.query.all() The implementation of Session.query_property() from SQLAlchemy and the query property installed by Flask-SA extensions are *identical* except for the useless access to _request_stack_ctx, I'm not sure why it wouldn't be better to have the first and official one...
Hi, On 2010-10-19 8:41 PM, Raphael Slinckx wrote: > The implementation of Session.query_property() from SQLAlchemy and the > query property installed by Flask-SA extensions are *identical* except > for the useless access to _request_stack_ctx, I'm not sure why it > wouldn't be better to have the first and official one... I already explained that it's necessary to dynamically dispatch to the application in order to figure out what the active application is. Models in Flask-SQLAlchemy can be used with more than one application. Now one could argue that in case app is passed to the SQLAlchemy constructor it might be possible to provide the query object all the time, but for that one has to create a proper patch plus testcase :) Regards, Armin
> I already explained that it's necessary to dynamically dispatch to the > application in order to figure out what the active application is. > Models in Flask-SQLAlchemy can be used with more than one application. Now I think I understand what's going on. When getting the Query property, the request_ctx check is not really needed (except for early failure), but a new scoped session is created ( by doing self.sa.session() ), this in turn creates a new SignallingSession, which will be bound to self.db.engine, which in turn will dispatch the the app to get the actual engine connection. This last step is the link between the app config settings and the 'global' query property. So Armin is right (hopefully!), if you have init-ed your Flask-SA instance with more than one app, you wouldn't know which config to use to create the engine. However, I now wonder, why not just keep a simple 1-1 mapping between db and app, and in that case create two instances of Flask-SA with the same declarative base but different sessions/binds if you want to share model between two apps (and two configs). It would then become obvious which config to use, it's the current thread's session which is bound to a single app. Or maybe I'm just wrong
Hi, On 2010-10-19 10:04 PM, Raphael Slinckx wrote: > However, I now wonder, why not just keep a simple 1-1 mapping between > db and app, and in that case create two instances of Flask-SA with the > same declarative base but different sessions/binds if you want to > share model between two apps (and two configs). It would then become > obvious which config to use, it's the current thread's session which > is bound to a single app. You would need Model.query_for_app_1 and Model.query_for_app_2 for this to work. Or do some meta magic to create a second model for the other app. It would also be possible to use db_for_app_1.query(Model) instead but neither is too nice. However I am totally for providing a way to remove the context local dependency for situations where the db is registered to just one app. I will try to figure out a nice solution for that. Also, the current error message just sucks. That can be solved nicer. Regards, Armin
SQLAlchemy holds several connections to the database, with each request one is acquired from the pool and a session object is created. Therefore you have to create a new query attribute from the current session for every request which is being made.
2010/10/14 Daniel Neuhäuser <dasdasich@googlemail.com>: > SQLAlchemy holds several connections to the database, with each request > one is acquired from the pool and a session object is created. Therefore > you have to create a new query attribute from the current session for > every request which is being made. According to the source code here: http://hg.sqlalchemy.org/sqlalchemy/file/d67812029db9/lib/sqlalchemy/orm/scoping.py#l98 It seems to do exactly that (by doing session=self.registry() which gets the current session)?