Is it possible to use relationship(lazy='raise') after session.refresh?

521 views
Skip to first unread message

Marat Sharafutdinov

unread,
Sep 25, 2019, 1:07:39 PM9/25/19
to sqlalchemy
from sqlalchemy import Column, ForeignKey, Integer, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import lazyload, relationship, sessionmaker

Base = declarative_base()


class Group(Base):
    __tablename__
= 'groups'

    id
= Column(Integer, primary_key=True)

    users
= relationship('User', lazy='raise')


class User(Base):
    __tablename__
= 'users'

    id
= Column(Integer, primary_key=True)
    group_id
= Column(Integer, ForeignKey('groups.id'))


engine
= create_engine('sqlite:///:memory:', echo=True)
Base.metadata.create_all(engine)

Session = sessionmaker(bind=engine)
session
= Session()

group = Group()
session
.add(group)
session
.commit()

group = session.query(Group).options(lazyload(Group.users)).first()
print('SUCCESS', group.users)
session
.refresh(group)
print('FAILURE', group.users)

Is `session.expire(group)` is the only way to reload collections and related items?

Mike Bayer

unread,
Sep 25, 2019, 3:58:24 PM9/25/19
to noreply-spamdigest via sqlalchemy
what you are seeing is somewhat backwards due to what might be a bug.    in the example you have, *both* calls to group.users should be raising, because this object is already present in the identity map so your lazyload() option should not affect this already-present Group object.

if you load the Group newly into a new Session, or session.close() the one you have, then the lazyload() option is associated with the Group and it will lazy load every time for the lifespan of that object.

I'm not sure yet why the lazyload() is somehow taking effect for the first group.users in your example, though.   it's not supposed to.
--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper
 
 
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.

Mike Bayer

unread,
Sep 25, 2019, 4:13:34 PM9/25/19
to noreply-spamdigest via sqlalchemy
OK, the first load is expected, because when you session.query(X).options(someload(X.y)), the query will load all the ".y" for objects that it finds already in the identity map, however it will not establish someload() as a new permanent option on that X going forward, so subsequent loads use the default loader.  if session.query(X).options(someload(X.y)) actually created the new X, then the someload() option is permanently associated with it.    I understand this is not completely clear but historically, all of these loader options were just optimizations and it wasn't the end of the world if in some refresh circumstances, they didn't take effect.    however with lazy='raise' it's a much bigger source of confusion.

There's an additional issue which is that in current 1.3 series, the call to refresh() does not make use of the loaders that were applied to the object .    So for current releases, it still wont work for you; the load after the refresh() will fail even if the X was just loaded.   However for 1.4 , it will work, due to https://docs.sqlalchemy.org/en/14/changelog/migration_14.html#change-1763 .

short answer is that in 1.3 if you are using lazy="raise", the object won't really have flexibility in being able to actually load that attribute other than accessing it with a query.   if you want, you can use this pattern, which should work everywhere:

session.query(Group).populate_existing().options(lazyload(Group.users)).get(group.id)

that also will apply the lazyload() option to the Group object permanently unless populate_existing() is used on it again.
Reply all
Reply to author
Forward
0 new messages