librelist archives

« back to archive

Flask-SQLAlchemy query property

Flask-SQLAlchemy query property

From:
Raphael Slinckx
Date:
2010-10-14 @ 21:44
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

Re: [flask] Flask-SQLAlchemy query property

From:
Armin Ronacher
Date:
2010-10-14 @ 23:30
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

Re: [flask] Flask-SQLAlchemy query property

From:
Michael Elsdörfer
Date:
2010-10-15 @ 16:01
 > 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

Re: [flask] Flask-SQLAlchemy query property

From:
Armin Ronacher
Date:
2010-10-15 @ 17:13
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

Re: [flask] Flask-SQLAlchemy query property

From:
Michael Elsdörfer
Date:
2010-10-18 @ 17:56
> 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

Re: [flask] Flask-SQLAlchemy query property

From:
Raphael Slinckx
Date:
2010-10-19 @ 18:41
> 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...

Re: [flask] Flask-SQLAlchemy query property

From:
Armin Ronacher
Date:
2010-10-19 @ 18:48
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

Re: [flask] Flask-SQLAlchemy query property

From:
Raphael Slinckx
Date:
2010-10-19 @ 20:04
> 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

Re: [flask] Flask-SQLAlchemy query property

From:
Armin Ronacher
Date:
2010-10-19 @ 20:21
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

Re: [flask] Flask-SQLAlchemy query property

From:
Daniel Neuhäuser
Date:
2010-10-14 @ 21:53
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.

Re: [flask] Flask-SQLAlchemy query property

From:
Raphael Slinckx
Date:
2010-10-14 @ 22:01
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)?