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]
.. 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
"""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
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 "
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)
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:
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, \
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):