On Mon, Oct 15, 2018 at 9:27 AM Simon King <si...@simonking.org.uk> 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.stalew...@nethone.com> 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+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. > > -- > 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. -- 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.