Query before_compile issue

60 views
Skip to first unread message

Eric Lemoine

unread,
Oct 22, 2019, 9:36:18 AM10/22/19
to sqlalchemy
Hi

I want to use the FilteredQuery pattern [1], but I am facing a problem related to SQLAlchemy not calling "before_compile" as often as I'd expect it.

Here's a simple example:

@event.listens_for(Query, "before_compile", retval=True)
def before_compile(query):
    print("XXXXX before_compiled called XXXXX")
    return query

class User(Base):
    __tablename__ = "user"
    id = Column(Integer, primary_key=True)
    name = Column(String)
    addresses = relationship("Address", back_populates="user")

class Address(Base):
    __tablename__ = "address"
    id = Column(Integer, primary_key=True)
    email = Column(String)
    user_id = Column(Integer, ForeignKey("user.id"))
    user = relationship("User", back_populates="addresses")

and then I do the following twice:

for u in Session().query(User): for a in u.addresses: print(u.name, a.email)

The first time before_compile is called twice, one call for the SELECT FROM "user" query, and another call for the SELECT FROM "address" query. But the second time before_compile is called only once. It's called for the SELECT FROM "user" query, but not for the SELECT FROM "addresses" query. Is it expected?

I am using SQLAlchemy 1.3.10.

The full test case is here: https://gist.github.com/elemoine/3fa86da54fc1195e314fa18999d05a68



Mike Bayer

unread,
Oct 22, 2019, 11:45:22 AM10/22/19
to noreply-spamdigest via sqlalchemy
sure, it sounds like u.addresses is already loaded.   if you want u.addresses to emit a query every time it is called you need to use the "dynamic" loader strategy, that is, lazy="dynamic".


--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper
 
 
To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description.
---
You received this message because you are subscribed to the Google Groups "sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+...@googlegroups.com.

Eric Lemoine

unread,
Oct 22, 2019, 11:58:06 AM10/22/19
to sqlalchemy

The first time before_compile is called twice, one call for the SELECT FROM "user" query, and another call for the SELECT FROM "address" query. But the second time before_compile is called only once. It's called for the SELECT FROM "user" query, but not for the SELECT FROM "addresses" query. Is it expected?

I am using SQLAlchemy 1.3.10.

sure, it sounds like u.addresses is already loaded.   if you want u.addresses to emit a query every time it is called you need to use the "dynamic" loader strategy, that is, lazy="dynamic".


I think the problem is related to "baked queries". I don't have the problem with enable_baked_queries set to False in sessionmaker.

Thanks for your response.

Mike Bayer

unread,
Oct 22, 2019, 11:59:47 AM10/22/19
to noreply-spamdigest via sqlalchemy
oh right that too, those queries are cached.   but whatever changes you made to the query should also be cached, how come you need it to be different every time?




Thanks for your response.


--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper
 
 
To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description.
---
You received this message because you are subscribed to the Google Groups "sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+...@googlegroups.com.

eric.l...@gmail.com

unread,
Oct 22, 2019, 12:12:41 PM10/22/19
to sqlal...@googlegroups.com


On Tue, Oct 22, 2019 at 5:59 PM Mike Bayer <mik...@zzzcomputing.com> wrote:


On Tue, Oct 22, 2019, at 11:58 AM, Eric Lemoine wrote:


The first time before_compile is called twice, one call for the SELECT FROM "user" query, and another call for the SELECT FROM "address" query. But the second time before_compile is called only once. It's called for the SELECT FROM "user" query, but not for the SELECT FROM "addresses" query. Is it expected?

I am using SQLAlchemy 1.3.10.

sure, it sounds like u.addresses is already loaded.   if you want u.addresses to emit a query every time it is called you need to use the "dynamic" loader strategy, that is, lazy="dynamic".


I think the problem is related to "baked queries". I don't have the problem with enable_baked_queries set to False in sessionmaker.

oh right that too, those queries are cached.   but whatever changes you made to the query should also be cached, how come you need it to be different every time?


I have HTTP queries with authenticated users associated to tenants. And I wanted to use the Filtered Query pattern to only select objects bound to the current tenant, without having to write complex queries (with explicit filters for the current tenant) in the request handling code. But that doesn't work, because of that "baked queries" issue. The Filtered Query example in the wiki page works because the filter is fixes (obj.public == True). In my case I want something like obj.tenant_id == get_tenant_id_from_request().

Thanks Mike.

--
Eric

Mike Bayer

unread,
Oct 22, 2019, 12:17:58 PM10/22/19
to noreply-spamdigest via sqlalchemy
it sounds like you should use a bound parameter with a lambda inside of it, there's not an explicit "on lazyload" hook at the moment.   but yeah turn off the baked queries does it too



Thanks Mike.

--
Eric


--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper
 
 
To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description.
---
You received this message because you are subscribed to the Google Groups "sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+...@googlegroups.com.

eric.l...@gmail.com

unread,
Oct 22, 2019, 12:34:32 PM10/22/19
to sqlal...@googlegroups.com

it sounds like you should use a bound parameter with a lambda inside of it, there's not an explicit "on lazyload" hook at the moment.

Yep,  that seems to be doing the job!

Mike Bayer

unread,
Oct 23, 2019, 9:16:25 AM10/23/19
to noreply-spamdigest via sqlalchemy


On Tue, Oct 22, 2019, at 12:30 PM, eric.l...@gmail.com wrote:



it sounds like you should use a bound parameter with a lambda inside of it, there's not an explicit "on lazyload" hook at the moment.

Yep,  that seems to be doing the job!


that worked?  wow

:)

guess you are using a threadlocal to get the context?



--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper
 
 
To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description.
---
You received this message because you are subscribed to the Google Groups "sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+...@googlegroups.com.

eric.l...@gmail.com

unread,
Oct 23, 2019, 10:10:19 AM10/23/19
to sqlal...@googlegroups.com
On Wed, Oct 23, 2019 at 3:16 PM Mike Bayer <mik...@zzzcomputing.com> wrote:


On Tue, Oct 22, 2019, at 12:30 PM, eric.l...@gmail.com wrote:



it sounds like you should use a bound parameter with a lambda inside of it, there's not an explicit "on lazyload" hook at the moment.

Yep,  that seems to be doing the job!


that worked?  wow

:)


SQLAlchemy always has a solution for me. But often uneasy to find :-)





guess you are using a threadlocal to get the context?


Yes indeed.


--
Eric

Mike Bayer

unread,
Oct 25, 2019, 10:51:33 AM10/25/19
to noreply-spamdigest via sqlalchemy
There's a long history, going back over a decade, that issues which have existed for a long time are suddenly reported in twos, that is, within a day of each other.


A recent example is just yesterday Alembic reporting both that type comparison *does* check the length of the types, and that it is mis-documented, and literally three hours earlier a report stating that it *does not* check the length:   https://github.com/sqlalchemy/alembic/issues/613  https://github.com/sqlalchemy/alembic/issues/612

today, we have this issue, which I can confirm has existed since version 1.2 over two years ago, reported for the first time ever here, and then a day later in https://github.com/sqlalchemy/sqlalchemy/issues/4947 .

I realized I can likely have the baked loader invalidate itself if before_compile occurs.   this will allow it to work for other cached loaders too like select in loading.

will try that now.
--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper
 
 
To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description.
---
You received this message because you are subscribed to the Google Groups "sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+...@googlegroups.com.

eric.l...@gmail.com

unread,
Oct 28, 2019, 2:56:54 AM10/28/19
to sqlal...@googlegroups.com




today, we have this issue, which I can confirm has existed since version 1.2 over two years ago, reported for the first time ever here, and then a day later in https://github.com/sqlalchemy/sqlalchemy/issues/4947 .


Even more funny is that this Issue uses a tenant in the provided test-case, which was exactly my use-case too!

Very good to see a proper fix for this!
Reply all
Reply to author
Forward
0 new messages