Yep, using populate_existing() in the second query makes test_2 work.

W dniu poniedziałek, 15 października 2018 15:41:23 UTC+2 użytkownik Mike 
Bayer napisał:
>
> On Mon, Oct 15, 2018 at 9:27 AM Simon King <si...@simonking.org.uk 
> <javascript:>> wrote: 
> > 
> > This is an interesting question. Here's my explanation of what's going 
> on: 
> > 
> > When you run a query using a loader option such as raiseload, 
> > SQLAlchemy constructs your instance and attaches the "raiseload" 
> > behaviour to that instance. In your test_1, this happens, but since 
> > you don't store the result, it is immediately garbage collected. You 
> > then run a second query, *without* the raiseload option, so SQLAlchemy 
> > constructs a second instance and doesn't trigger an exception when you 
> > access the "bs" property. 
> > 
> > In test_2, you save the result of the first query. It is not garbage 
> > collected, so it continues to exist inside SQLAlchemy's identity map, 
> > and it has the "raiseload" behaviour. When you run the second query, 
> > SQLAlchemy notices that the session *already contains* the instance 
> > that you queried for, so it returns that instance. It still has the 
> > raiseload behaviour, so you get an exception when you access the "bs" 
> > property. 
> > 
> > So what you are really asking is "should relationship-loading 
> > behaviour be stripped from instances already in the session when they 
> > are returned from a query which did not specify those options?". I 
> > suspect that the answer is no, but I don't know if it is actually 
> > specified anywhere. 
>
> I'm pretty sure if you run the query with populate_existing(), then it 
> would actually do this.   but if you don't, then it doesn't affect 
> instances that are already present. 
>
>
> > 
> > Does that make sense? 
> > 
> > Simon 
> > 
> > On Mon, Oct 15, 2018 at 1:49 PM Bartosz Stalewski 
> > <bartosz....@nethone.com <javascript:>> wrote: 
> > > 
> > > Hi, 
> > > 
> > > I observed some strange (for me) behavior related to setting query 
> options. I am not sure if it works as intended or it is a bug. 
> > > It seems to me that options are applied to query in a strange manner. 
> The posted below the code that show this behavior: 
> > > 1) in both tests I am configuring sqlalchemy to raise an exception 
> when A.bs relation is accessed 
> > > 2) but in test number 1, I am not assigning result of running query 
> (i.e. I am not reading it as I understand, since it is lazy) and exception 
> is not raised. 
> > > 3) on the other hand in test number 2, I am assigning this result to 
> variable 'x' and an exception is raised (this is the only difference 
> between tests). 
> > > 4) It does not matter if I run test_1 prior to test_2. 
> > > 5) I tested it on current (1.2.12) version of sqlalchemy. 
> > > 
> > > Documentation (
> https://docs.sqlalchemy.org/en/latest/orm/loading_relationships.html#controlling-loading-via-options)
>  
> suggests that setting this option should be possible in 
> > > the way I am doing it in test 1, but I think that my case shows that 
> it does not. 
> > > 
> > > Does it work as it is supposed to work and I am missing something or 
> is it a bug? 
> > > 
> > > Creating and populating tables: 
> > > 
> > > from sqlalchemy import Column, Integer, String, orm, ForeignKey, 
> create_engine 
> > > from sqlalchemy.orm import relationship, Session 
> > > from sqlalchemy.ext.declarative import declarative_base 
> > > 
> > > Base = declarative_base() 
> > > 
> > > 
> > > class A(Base): 
> > >     __tablename__ = 'a' 
> > >     id = Column(Integer, primary_key=True) 
> > >     name = Column(String) 
> > > 
> > >     def __repr__(self): return '<A id: {}, name: {}>'.format(self.id, 
> self.name) 
> > > 
> > > 
> > > class B(Base): 
> > >     __tablename__ = 'b' 
> > >     id = Column(Integer, primary_key=True) 
> > > 
> > >     a_id = Column(Integer, ForeignKey('a.id'), index=True) 
> > >     a = relationship('A', backref='bs') 
> > > 
> > >     def __repr__(self): return '<B id: {}, a_id: {}>'.format(self.id, 
> self.a_id) 
> > > 
> > > 
> > > e = create_engine("sqlite://", echo=True) 
> > > Base.metadata.create_all(e) 
> > > 
> > > s = Session(e) 
> > > s.add(A(id=1, name='A1', bs=[B()])) 
> > > s.add(A(id=2, name='A2', bs=[B(), B()])) 
> > > s.commit() 
> > > 
> > > 
> > > 
> > > Tests definition: 
> > > 
> > > def test_1(): 
> > >     # This DOES NOT affect further queries 
> > >     print('------------------------ Test 1 ------------------------') 
> > >     s.query(A).options(orm.raiseload(A.bs)).filter_by(id=1).all() 
> > >     print(s.query(A).filter_by(id=1).all()[0].bs) 
> > > 
> > > 
> > > def test_2(): 
> > >     # This DOES affect further queries 
> > >     print('------------------------ Test 2 ------------------------') 
> > >     x = s.query(A).options(orm.raiseload(A.bs)).filter_by(id=1).all() 
> > >     print(s.query(A).filter_by(id=1).all()[0].bs) 
> > > 
> > > 
> > > test_1() 
> > > test_2() 
> > > 
> > > 
> > > test_1 does not raise any exception, while test_2 raises: 
> > > 
> > > 
> --------------------------------------------------------------------------- 
> > > InvalidRequestError                       Traceback (most recent call 
> last) 
> > > <ipython-input-2-6aad37f5c088> in <module> 
> > >      24 create_data() 
> > >      25 test_1() 
> > > ---> 26 test_2() 
> > > 
> > > <ipython-input-2-6aad37f5c088> in test_2() 
> > >      19     print('------------------------ Test 2 
> ------------------------') 
> > >      20     x = 
> s.query(A).options(orm.raiseload(A.bs)).filter_by(id=1).all() 
> > > ---> 21     print(s.query(A).filter_by(id=1).all()[0].bs) 
> > >      22 
> > >      23 
> > > 
> > > 
> ~/Envs/importer/lib/python3.6/site-packages/sqlalchemy/orm/attributes.py in 
> __get__(self, instance, owner) 
> > >     240             return dict_[self.key] 
> > >     241         else: 
> > > --> 242             return self.impl.get(instance_state(instance), 
> dict_) 
> > >     243 
> > >     244 
> > > 
> > > 
> ~/Envs/importer/lib/python3.6/site-packages/sqlalchemy/orm/attributes.py in 
> get(self, state, dict_, passive) 
> > >     597                 elif key in state.callables: 
> > >     598                     callable_ = state.callables[key] 
> > > --> 599                     value = callable_(state, passive) 
> > >     600                 elif self.callable_: 
> > >     601                     value = self.callable_(state, passive) 
> > > 
> > > 
> ~/Envs/importer/lib/python3.6/site-packages/sqlalchemy/orm/strategies.py in 
> __call__(self, state, passive) 
> > >     832         strategy = prop._strategies[self.strategy_key] 
> > >     833 
> > > --> 834         return strategy._load_for_state(state, passive) 
> > >     835 
> > >     836 
> > > 
> > > 
> ~/Envs/importer/lib/python3.6/site-packages/sqlalchemy/orm/strategies.py in 
> _load_for_state(self, state, passive) 
> > >     587 
> > >     588         if self._raise_always: 
> > > --> 589             self._invoke_raise_load(state, passive, "raise") 
> > >     590 
> > >     591         session = _state_session(state) 
> > > 
> > > 
> ~/Envs/importer/lib/python3.6/site-packages/sqlalchemy/orm/strategies.py in 
> _invoke_raise_load(self, state, passive, lazy) 
> > >     562     def _invoke_raise_load(self, state, passive, lazy): 
> > >     563         raise sa_exc.InvalidRequestError( 
> > > --> 564             "'%s' is not available due to lazy='%s'" % (self, 
> lazy) 
> > >     565         ) 
> > >     566 
> > > 
> > > InvalidRequestError: 'A.bs' is not available due to lazy='raise' 
> > > 
> > > 
> > > -- 
> > > SQLAlchemy - 
> > > The Python SQL Toolkit and Object Relational Mapper 
> > > 
> > > http://www.sqlalchemy.org/ 
> > > 
> > > 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 <javascript:>. 
> > > To post to this group, send email to sqlal...@googlegroups.com 
> <javascript:>. 
> > > Visit this group at https://groups.google.com/group/sqlalchemy. 
> > > For more options, visit https://groups.google.com/d/optout. 
> > 
> > -- 
> > SQLAlchemy - 
> > The Python SQL Toolkit and Object Relational Mapper 
> > 
> > http://www.sqlalchemy.org/ 
> > 
> > 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 <javascript:>. 
> > To post to this group, send email to sqlal...@googlegroups.com 
> <javascript:>. 
> > Visit this group at https://groups.google.com/group/sqlalchemy. 
> > For more options, visit https://groups.google.com/d/optout. 
>

-- 
SQLAlchemy - 
The Python SQL Toolkit and Object Relational Mapper

http://www.sqlalchemy.org/

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+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

Reply via email to