On Fri, Sep 28, 2018 at 9:41 PM seaders <seader...@gmail.com> wrote:
>
> I'm writing some tests for my system, and I have all the objects that I 
> *would* be getting from the database, as flat json files, which I then load 
> up from, to SQLAlchemy files for my tests.
>
> I've all that working fine, but the one place I'd like to improve is "gluing" 
> them back together / creating the mapping associations.
>
> Consider the following,
>
> class Sport(Base):
>     id = Column(Integer, primary_key=True)
>     name = Column(String(50), unique=True)
>
> class Competition(Base):
>     id = Column(Integer, primary_key=True)
>     name = Column(String(50), unique=True)
>
>     sport_id = Column(Integer, ForeignKey(Sport.id))
>     sport = relationship(Sport)
>
>
> If I run
>
> sport = Sport.query.filter(Sport.id == 1).first()
> comp = Competition.query.filter(Competition.sport == sport)
> assert sport == comp.sport
>
>
> Everything's good.
>
> However, if I create a Competition as a "flat" object,
>
> comp2 = Competition(id=2345, name='My Comp', sport_id=1)
> session.add(comp2)
> assert sport == comp2.sport
>
>
> This one fails, because sport on comp2 is null.
>
> Poking through the code, I see it's mainly / probably to do with the logic 
> for _load_for_state in strategies,
>
> def _load_for_state(self, state, passive):
>
>     if not state.key and (
>             (
>                 not self.parent_property.load_on_pending
>                 and not state._load_pending
>             )
>             or not state.session_id
>     ):
>         return attributes.ATTR_EMPTY
>
>
> comp has a state.key for it's sport column, whereas comp2 doesn't (and both 
> self.parent_property.load_on_pending and state._load_pending are false.
>
> So yeah, that's my overall question.  With an object that's *not* gone into, 
> nor come back from the database, is there some global option, or something 
> along those lines so that when I check properties of those objects, the 
> mapper does its thing, and maps to its objects, and not ATTR_EMPTY / None?

yes that's what those two flags are doing.     the way the ORM is
designed, it wants you to be setting comp.sport = sport, not
comp.sport_id = id, but there's a fair degree of special features
these days that allow people to work the other way since it is so
commonly requested, which are specifically what those two flags are
for.

an overview of the situation is at
https://docs.sqlalchemy.org/en/latest/faq/sessions.html#i-set-the-foo-id-attribute-on-my-instance-to-7-but-the-foo-attribute-is-still-none-shouldn-t-it-have-loaded-foo-with-id-7,
which is probably the first FAQ question we've ever had.  The API
features that correspond to load_on_pending and load_pending are the
load_on_pending relationship flag:
https://docs.sqlalchemy.org/en/latest/orm/relationship_api.html?highlight=load_on_pending#sqlalchemy.orm.relationship.params.load_on_pending
, and enable_relationship_loading at
https://docs.sqlalchemy.org/en/latest/orm/session_api.html#sqlalchemy.orm.session.Session.enable_relationship_loading,
which does the same thing with a different scope, that is, for
individual objects rather than an entire mapping.

both APIs do a similar thing, it's just that load_on_pending is set at
the level of the class mapping on the relationship, whereas
enable_relationship_loading you can just enable for any individual
object without any change to the mapping which may be better for
running tests.

since I never use these I feel I had to test them out to make sure I'm
not mis-characterizing, so it looks like this.  here is
load_on_pending:

class A(Base):
    __tablename__ = 'a'

    id = Column(Integer, primary_key=True)
    bs = relationship("B", backref=backref("a", load_on_pending=True))


class B(Base):
    __tablename__ = 'b'
    id = Column(Integer, primary_key=True)
    a_id = Column(ForeignKey("a.id"))


s = Session(e)

a1 = A(id=1)
s.add(a1)
s.commit()

b1 = B(a_id=1)
s.add(b1)   # note you have to add b1 to the session for it to be "pending"
assert b1.a is a1

Here is enable_relationship_loading:

class A(Base):
    __tablename__ = 'a'

    id = Column(Integer, primary_key=True)
    bs = relationship("B", backref="a")


class B(Base):
    __tablename__ = 'b'
    id = Column(Integer, primary_key=True)
    a_id = Column(ForeignKey("a.id"))


a1 = A(id=1)
s.add(a1)
s.commit()

b1 = B(a_id=1)
s.enable_relationship_loading(b1)   # note you *don't* have to add to
the session for this
assert b1.a is a1

you can also add b1 to the session at any time above.

hope this helps.






>
>
> --
> 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