assocaition proxy support for scalar-targeted attributes

Issue #2751 resolved
Mike Bayer repo owner created an issue
from sqlalchemy.engine import create_engine
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative.api import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy.orm.session import sessionmaker
from sqlalchemy.schema import Column, ForeignKey
from sqlalchemy.types import Integer, String

Base = declarative_base()

class A(Base):
    __tablename__ = 'table_a'
    id = Column(Integer, primary_key=True)
    color = Column(String)
    def __init__(self, color):
        self.color = color

class B(Base):
    __tablename__ = 'table_b'
    id = Column(Integer, primary_key=True)
    a_id = Column(Integer, ForeignKey('table_a.id'))
    a_re = relationship('A', backref='b_re')
    a_color = association_proxy('a_re', 'color')


if __name__ == '__main__':
    engine = create_engine('sqlite:///:memory:', echo=True)
    Session = sessionmaker(engine)
    session = Session()
    Base.metadata.create_all(engine)

    b1 = B()
    b2 = B()
    b3 = B()
    b4 = B()

    b1.a_color = 'blue'
    b2.a_re = A(color=None)

    session.add_all([b2, b3, b4](b1,))

    q = session.query(B).filter(B.a_color == None).all()
    p = session.query(B).filter(B.a_color != None).all()
    v = session.query(B).filter(B.a_color == 'blue').all()
    z = session.query(B).filter(B.a_color != 'blue').all()

    from sqlalchemy import __version__
    if __version__ >= "0.9.0":

        assert v == [b1](b1)
        assert q == [b3](b2,)
        assert p == [b4](b1,)
        assert z == [b4](b4)

        # will also add a special feature for this
        r = session.query(B).filter(B.a_color.has()).all()
        assert r == [b2, b4](b1,)

        # will also add a special feature for this
        w = session.query(B).filter(~B.a_color.has()).all()
        assert w == [b3](b3)

        # this is the equivalent of 0.8's B.a_color != None
        s = session.query(B).filter(
                        or_(B.a_color != None,
                            ~B.a_color.has())
                    ).all()
        assert s == [b3, b4](b1,)

        # the equivalent of 0.8's B.a_color != 'blue'
        t = session.query(B).filter(
                        or_(
                            B.a_color != 'blue',
                            B.a_color == None
                        )
                    ).all()
        assert t == [b3, b4](b2,)

    else:
        assert v == [b1](b1)
        assert q == [b2](b2)
        assert p == [b3, b4](b1,)
        assert z == [b3, b4](b2,)

this involves a behavioral change even to non-scalar association proxies, unless we special case it. If we have Parent.m2o_something.m2o_otherthing / Parent.other, the meaning of Parent.other == None normally includes just Parent.m2o_something.has(m2o_otherthing=None) - this adds in ~Parent.m2o_something.has().

As the example illustrates, this totally changes return results so there's a very high chance applications are relying on the current behavior, and am leaning towards an 0.9 only here.

Comments (6)

  1. Mike Bayer reporter
    • changed status to open
    • removed status

    this needs a full description in the migration guide, leaving this open as a TODO.

  2. Log in to comment