question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Missing join-loaded relations for polymorphic models

See original GitHub issue

We 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:closed
  • Created 4 years ago
  • Comments:7 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
davidparssoncommented, Jan 31, 2020

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.

1reaction
zzzeekcommented, Jan 31, 2020

also fully perfect test case, thanks very much for taking the time to do a very good job on this.

Read more comments on GitHub >

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

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found