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.

Reply via email to