inconsistency in concrete relationships w/ name overlap in relationship() vs. backref

Issue #3630 resolved
Mike Bayer repo owner created an issue

this is extracted from #3022. test:

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

err = True

class A(Base):
    __tablename__ = 'a'
    id = Column(Integer, primary_key=True)

    if not err:
        b = relationship("B", foreign_keys="B.a_id", backref="a")


class A1(A):
    __tablename__ = 'a1'
    id = Column(Integer, primary_key=True)

    if not err:
        b = relationship("B", foreign_keys="B.a1_id", backref="a1")
    __mapper_args__ = {'concrete': True}


class B(Base):
    __tablename__ = 'b'
    id = Column(Integer, primary_key=True)

    a_id = Column(ForeignKey('a.id'))
    a1_id = Column(ForeignKey('a1.id'))

    if err:
        a = relationship("A", backref="b")

        a1 = relationship("A1", backref="b")

configure_mappers()

Comments (4)

  1. Mike Bayer reporter

    here's a patch:

    diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
    index 95aa14a..88dadcc 100644
    --- a/lib/sqlalchemy/orm/mapper.py
    +++ b/lib/sqlalchemy/orm/mapper.py
    @@ -1591,7 +1591,12 @@ class Mapper(InspectionAttr):
    
             if key in self._props and \
                     not isinstance(prop, properties.ColumnProperty) and \
    -                not isinstance(self._props[key], properties.ColumnProperty):
    +                not isinstance(
    +                    self._props[key],
    +                    (
    +                        properties.ColumnProperty,
    +                        properties.ConcreteInheritedProperty)
    +                ):
                 util.warn("Property %s on %s being replaced with new "
                           "property %s; the old property will be discarded" % (
                               self._props[key],
    diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py
    index f822071..9b02d86 100644
    --- a/lib/sqlalchemy/orm/relationships.py
    +++ b/lib/sqlalchemy/orm/relationships.py
    @@ -1817,15 +1817,16 @@ class RelationshipProperty(StrategizedProperty):
                     backref_key, kwargs = self.backref
                 mapper = self.mapper.primary_mapper()
    
    -            check = set(mapper.iterate_to_root()).\
    -                union(mapper.self_and_descendants)
    -            for m in check:
    -                if m.has_property(backref_key):
    -                    raise sa_exc.ArgumentError(
    -                        "Error creating backref "
    -                        "'%s' on relationship '%s': property of that "
    -                        "name exists on mapper '%s'" %
    -                        (backref_key, self, m))
    +            if not mapper.concrete:
    +                check = set(mapper.iterate_to_root()).\
    +                    union(mapper.self_and_descendants)
    +                for m in check:
    +                    if m.has_property(backref_key):
    +                        raise sa_exc.ArgumentError(
    +                            "Error creating backref "
    +                            "'%s' on relationship '%s': property of that "
    +                            "name exists on mapper '%s'" %
    +                            (backref_key, self, m))
    
                 # determine primaryjoin/secondaryjoin for the
                 # backref.  Use the one we had, so that
    
  2. Mike Bayer reporter
    • Fixed issue where two same-named relationships that refer to a base class and a concrete-inherited subclass would raise an error if those relationships were set up using "backref", while setting up the identical configuration using relationship() instead with the conflicting names would succeed, as is allowed in the case of a concrete mapping. fixes #3630

    → <<cset b7bc704f3d05>>

  3. Mike Bayer reporter
    • the order in which _generate_backref() for different mappers is called is random; therefore it may be called against the subclass mapper first, so need to check .concrete on both sides, references #3630

    → <<cset 5a279e7ae441>>

  4. Log in to comment