]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- A quasi-regression where apparently in 0.8 you can set a class-level
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 2 Jan 2014 23:51:49 +0000 (18:51 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 2 Jan 2014 23:51:49 +0000 (18:51 -0500)
attribute on declarative to simply refer directly to an :class:`.InstrumentedAttribute`
on a superclass or on the class itself, and it
acts more or less like a synonym; in 0.9, this fails to set up enough
bookkeeping to keep up with the more liberalized backref logic
from :ticket:`2789`.  Even though this use case was never directly
considered, it is now detected by declarative at the "setattr()" level
as well as when setting up a subclass, and the mirrored/renamed attribute
is now set up as a :func:`.synonym` instead. [ticket:2900]

doc/build/changelog/changelog_09.rst
lib/sqlalchemy/ext/declarative/base.py
test/ext/declarative/test_basic.py

index 069df37a4cafbcc50662cc47938abba3dafce8be..783c674fd7d34dc29708970a3574a65dee2a2784 100644 (file)
 .. changelog::
     :version: 0.9.1
 
+    .. change::
+        :tags: bug, orm, declarative
+        :tickets: 2900
+
+        A quasi-regression where apparently in 0.8 you can set a class-level
+        attribute on declarative to simply refer directly to an :class:`.InstrumentedAttribute`
+        on a superclass or on the class itself, and it
+        acts more or less like a synonym; in 0.9, this fails to set up enough
+        bookkeeping to keep up with the more liberalized backref logic
+        from :ticket:`2789`.  Even though this use case was never directly
+        considered, it is now detected by declarative at the "setattr()" level
+        as well as when setting up a subclass, and the mirrored/renamed attribute
+        is now set up as a :func:`.synonym` instead.
+
     .. change::
         :tags: bug, orm
         :tickets: 2903
index f7668a54017ab466eb6f35394c82d688b782d627..69e4b9eeafa67176cb74a93c55cea3450f103af9 100644 (file)
@@ -6,9 +6,10 @@
 """Internal implementation for declarative."""
 
 from ...schema import Table, Column
-from ...orm import mapper, class_mapper
+from ...orm import mapper, class_mapper, synonym
 from ...orm.interfaces import MapperProperty
 from ...orm.properties import ColumnProperty, CompositeProperty
+from ...orm.attributes import QueryableAttribute
 from ...orm.base import _is_mapped_class
 from ... import util, exc
 from ...sql import expression
@@ -148,6 +149,15 @@ def _as_declarative(cls, classname, dict_):
         if isinstance(value, declarative_props):
             value = getattr(cls, k)
 
+        elif isinstance(value, QueryableAttribute) and \
+                value.class_ is not cls and \
+                value.key != k:
+            # detect a QueryableAttribute that's already mapped being
+            # assigned elsewhere in userland, turn into a synonym()
+            value = synonym(value.key)
+            setattr(cls, k, value)
+
+
         if (isinstance(value, tuple) and len(value) == 1 and
             isinstance(value[0], (Column, MapperProperty))):
             util.warn("Ignoring declarative-like tuple value of attribute "
@@ -397,6 +407,7 @@ def _add_attribute(cls, key, value):
     adds it to the Mapper, adds a column to the mapped Table, etc.
 
     """
+
     if '__mapper__' in cls.__dict__:
         if isinstance(value, Column):
             _undefer_column_name(key, value)
@@ -413,6 +424,14 @@ def _add_attribute(cls, key, value):
                 key,
                 clsregistry._deferred_relationship(cls, value)
             )
+        elif isinstance(value, QueryableAttribute) and value.key != key:
+            # detect a QueryableAttribute that's already mapped being
+            # assigned elsewhere in userland, turn into a synonym()
+            value = synonym(value.key)
+            cls.__mapper__.add_property(
+                key,
+                clsregistry._deferred_relationship(cls, value)
+            )
         else:
             type.__setattr__(cls, key, value)
     else:
index 0d213fce3dc9b4cca92ced8211d68d68a7be1fc1..1f14d81645ec6bb5b925ea1e7573b255c643c1d6 100644 (file)
@@ -11,7 +11,7 @@ from sqlalchemy.testing.schema import Table, Column
 from sqlalchemy.orm import relationship, create_session, class_mapper, \
     joinedload, configure_mappers, backref, clear_mappers, \
     deferred, column_property, composite,\
-    Session
+    Session, properties
 from sqlalchemy.testing import eq_
 from sqlalchemy.util import classproperty, with_metaclass
 from sqlalchemy.ext.declarative import declared_attr, AbstractConcreteBase, \
@@ -792,6 +792,64 @@ class DeclarativeTest(DeclarativeTestBase):
         eq_(a1, Address(email='two'))
         eq_(a1.user, User(name='u1'))
 
+    def test_alt_name_attr_subclass_column_inline(self):
+        # [ticket:2900]
+        class A(Base):
+            __tablename__ = 'a'
+            id = Column('id', Integer, primary_key=True)
+            data = Column('data')
+
+        class ASub(A):
+            brap = A.data
+        assert ASub.brap.property is A.data.property
+        assert isinstance(ASub.brap.original_property, properties.SynonymProperty)
+
+    def test_alt_name_attr_subclass_relationship_inline(self):
+        # [ticket:2900]
+        class A(Base):
+            __tablename__ = 'a'
+            id = Column('id', Integer, primary_key=True)
+            b_id = Column(Integer, ForeignKey('b.id'))
+            b = relationship("B", backref="as_")
+
+        class B(Base):
+            __tablename__ = 'b'
+            id = Column('id', Integer, primary_key=True)
+
+        configure_mappers()
+        class ASub(A):
+            brap = A.b
+        assert ASub.brap.property is A.b.property
+        assert isinstance(ASub.brap.original_property, properties.SynonymProperty)
+        ASub(brap=B())
+
+    def test_alt_name_attr_subclass_column_attrset(self):
+        # [ticket:2900]
+        class A(Base):
+            __tablename__ = 'a'
+            id = Column('id', Integer, primary_key=True)
+            data = Column('data')
+        A.brap = A.data
+        assert A.brap.property is A.data.property
+        assert isinstance(A.brap.original_property, properties.SynonymProperty)
+
+    def test_alt_name_attr_subclass_relationship_attrset(self):
+        # [ticket:2900]
+        class A(Base):
+            __tablename__ = 'a'
+            id = Column('id', Integer, primary_key=True)
+            b_id = Column(Integer, ForeignKey('b.id'))
+            b = relationship("B", backref="as_")
+        A.brap = A.b
+        class B(Base):
+            __tablename__ = 'b'
+            id = Column('id', Integer, primary_key=True)
+
+        assert A.brap.property is A.b.property
+        assert isinstance(A.brap.original_property, properties.SynonymProperty)
+        A(brap=B())
+
+
     def test_eager_order_by(self):
 
         class Address(Base, fixtures.ComparableEntity):