Missing join-loaded relations for polymorphic models
See original GitHub issueWe have found what seems to be a bug in SQLAlchemy’s lazy="joined"
loading. It is present in the last stable public version, 1.3.13
.
There are a few conditions that together may cause a Parent to miss some related Children when loaded:
- The Parent is polymorphic
- The Parent has join-loaded Children
- The Parent is loaded from a Grand Parent
- The Parent has an attribute that is accessed in
InstanceEvents.load()
Below is a reproducible, probably minimal but not so small example:
import enum
import sqlalchemy
from sqlalchemy import Column
from sqlalchemy import Enum
from sqlalchemy import event
from sqlalchemy import Float
from sqlalchemy import ForeignKey
from sqlalchemy import inspect
from sqlalchemy import Integer
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
class DefaultBase(Base):
id = Column(Integer, primary_key=True, autoincrement=True)
__abstract__ = True
def __repr__(self):
return f"<{self.__class__.__name__} id={self.id}>"
class GrandParent(DefaultBase):
parent_id = Column(Integer, ForeignKey("parent.id"))
parent = relationship("Parent")
__tablename__ = "grand_parent"
class ParentType(enum.Enum):
TYPE_A = "Type A"
class Parent(DefaultBase):
parent_type = Column(Enum(ParentType, name="parent_type"))
children = relationship("Child", lazy="joined")
__tablename__ = "parent"
__mapper_args__ = {"polymorphic_on": parent_type}
class ParentA(Parent):
id = Column(Integer, ForeignKey(Parent.id), primary_key=True)
some_float = Column(Float()) # This value will be loaded in on_load
__tablename__ = "parent_a"
__mapper_args__ = {"polymorphic_identity": ParentType.TYPE_A}
class Child(DefaultBase):
parent_id = Column(Integer, ForeignKey(Parent.id))
__tablename__ = "child"
@event.listens_for(ParentA, "load", propagate=True)
def on_load(instance, context):
instance_state = inspect(instance)
_ = instance_state.attrs["some_float"].value # Reading a value is required
def main():
session = setup_database_and_session()
children = [Child(), Child()]
parent = ParentA(children=children)
grand_parent = GrandParent(parent=parent)
session.add(grand_parent)
session.commit()
session.expunge_all()
grand_parent = session.query(GrandParent).one()
parent = grand_parent.parent # Fetching from grand_parent is required
before = parent.children.copy() # [<Child id=1>] # Incorrect!
session.rollback()
after = parent.children.copy() # [<Child id=1>, <Child id=2>] # Correct!
assert before == after, f"{before} != {after}"
def setup_database_and_session():
engine = sqlalchemy.create_engine("sqlite://")
session_maker = sqlalchemy.orm.sessionmaker(bind=engine)
session = session_maker()
Base.metadata.create_all(engine)
return session
if __name__ == "__main__":
main()
The example fails with the following assertion error:
AssertionError: [<Child id=1>] != [<Child id=1>, <Child id=2>]
It seems lazy="subquery"
also has similar issues, but not identical, and while we have observed issues we have not fully reproduced them.
Issue Analytics
- State:
- Created 4 years ago
- Comments:7 (3 by maintainers)
Top Results From Across the Web
Relationship Loading Techniques
Very detailed control over relationship loading is available using loader options; the most common are joinedload() , subqueryload() ...
Read more >sqlalchemy nested inheritance / polymorphic relationships
The code below is using the declarative_base , but shows the model setup which works. D class is both a parent and a...
Read more >Disabling subqueries when using joinedload to load a ...
I have a class, Test, with a single polymorphic subclass, TestOne. I also have TestChild, which has a fk to Test, and a...
Read more >Eager loading Polymorphic relationship. Need help - Laracasts
Call to undefined relationship [product] on model [App\Event]. My model structure is below. What am i missing? Modals: Businesses Modal. Copy Code
Read more >Polymorphic relationships in Laravel and their use cases
It's not uncommon in software development to have models that can belong to more than one entity. This type of model maintains the...
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
This is brilliant! I was not expecting such a quick response, and with both a fix and a workaround. Thank you! 🏆
I’ll keep an eye out for the subquery issue.
also fully perfect test case, thanks very much for taking the time to do a very good job on this.