Skip to content

support adapt_on_names for with_polymorphic #7805

Closed
@zzzeek

Description

@zzzeek

Discussed in #7797

here's the full integration case

from sqlalchemy import Column
from sqlalchemy import create_engine
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import literal
from sqlalchemy import null
from sqlalchemy import select
from sqlalchemy import String
from sqlalchemy import union
from sqlalchemy.ext.declarative import AbstractConcreteBase
from sqlalchemy.orm import aliased
from sqlalchemy.orm import composite
from sqlalchemy.orm import contains_eager
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import relationship
from sqlalchemy.orm import Session
from sqlalchemy.orm import with_polymorphic


Base = declarative_base()


class Metadata(Base):
    __tablename__ = "metadata"
    id = Column(
        Integer,
        primary_key=True,
        unique=True,
    )
    some_data = Column(String)


def make_statement(*filter_cond, include_metadata=False):

    a_stmt = (
        select(
            A.id,
            A.thing1,
            A.x1,
            A.y1,
            null().label("thing2"),
            null().label("x2"),
            null().label("y2"),
            literal("a").label("type"),
        )
        .join(Metadata)
        .filter(*filter_cond)
    )
    if include_metadata:
        a_stmt = a_stmt.add_columns(
            Metadata.__table__
        )

    b_stmt = (
        select(
            B.id,
            null().label("thing1"),
            null().label("x1"),
            null().label("y1"),
            B.thing2,
            B.x2,
            B.y2,
            literal("b").label("type"),
        )
        .join(Metadata)
        .filter(*filter_cond)
    )
    if include_metadata:
        b_stmt = b_stmt.add_columns(Metadata.__table__)

    return union(a_stmt, b_stmt)


class BaseObj(AbstractConcreteBase, Base):
    @declared_attr
    def id(cls):
        return Column(ForeignKey(Metadata.id), primary_key=True)

    @classmethod
    def _create_polymorphic_union(cls, mappers, discriminator_name):
        return make_statement().subquery()

    @declared_attr
    def related_metadata(cls):
        return relationship(Metadata)


class XYThing:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __composite_values__(self):
        return (self.x, self.y)

    def __repr__(self):
        return f"XYThing({self.x}, {self.y})"


class A(BaseObj):
    __tablename__ = "a"
    thing1 = Column(String)
    comp1 = composite(XYThing, Column("x1", Integer), Column("y1", Integer))

    @declared_attr
    def __mapper_args__(cls):
        return {"polymorphic_identity": cls.__tablename__, "concrete": True}

    def __repr__(self):
        return f"A(id={self.id}, thing1={self.thing1}, comp1={self.comp1})"


class B(BaseObj):
    __tablename__ = "b"
    thing2 = Column(String)
    comp2 = composite(XYThing, Column("x2", Integer), Column("y2", Integer))

    @declared_attr
    def __mapper_args__(cls):
        return {"polymorphic_identity": cls.__tablename__, "concrete": True}

    def __repr__(self):
        return f"B(id={self.id}, thing2={self.thing2}, comp2={self.comp2})"


e = create_engine("postgresql://scott:tiger@localhost/test", echo="debug")
Base.metadata.drop_all(e)
Base.metadata.create_all(e)

with Session(e) as sess:
    sess.add_all([Metadata(id=1, some_data="m1"), Metadata(id=2, some_data="m2")])
    sess.flush()

    sess.add_all(
        [
            A(id=1, thing1="thing1", comp1=XYThing(1, 2)),
            B(id=2, thing2="thing2", comp2=XYThing(3, 4)),
        ]
    )
    sess.commit()


# version one - use selectinload

with Session(e) as sess:

    alias = make_statement(Metadata.id < 3, include_metadata=True).subquery()
    ac = with_polymorphic(
        BaseObj,
        [A, B],
        selectable=alias,
        adapt_on_names=True,
    )

    mt = aliased(Metadata, alias=alias)

    for obj in sess.scalars(
        select(ac).options(
            contains_eager(ac.A.related_metadata.of_type(mt)),
            contains_eager(ac.B.related_metadata.of_type(mt)),
        )
    ):

        print(obj)
        print(obj.related_metadata)

Activity

added
inheritanceissues to do wtih ORM inheritance, a particularly tricky area
use casenot really a feature or a bug; can be support for new DB features or user use cases not anticipated
on Mar 8, 2022
added this to the 1.4.x milestone on Mar 8, 2022
sqla-tester

sqla-tester commented on Mar 8, 2022

@sqla-tester
Collaborator

Mike Bayer has proposed a fix for this issue in the main branch:

support adapt_on_names for with_polymorphic https://gerrit.sqlalchemy.org/c/sqlalchemy/sqlalchemy/+/3658

sqla-tester

sqla-tester commented on Mar 8, 2022

@sqla-tester
Collaborator

Mike Bayer has proposed a fix for this issue in the rel_1_4 branch:

support adapt_on_names for with_polymorphic https://gerrit.sqlalchemy.org/c/sqlalchemy/sqlalchemy/+/3659

added a commit that references this issue on Mar 9, 2022
78c0d4f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    inheritanceissues to do wtih ORM inheritance, a particularly tricky areaormuse casenot really a feature or a bug; can be support for new DB features or user use cases not anticipated

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @zzzeek@sqla-tester

        Issue actions

          support adapt_on_names for with_polymorphic · Issue #7805 · sqlalchemy/sqlalchemy