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.