- 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 🎫`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]
This commit is contained in:
Mike Bayer
2014-01-02 18:51:49 -05:00
parent ca8fca6391
commit 8a7fdd4e5c
3 changed files with 93 additions and 2 deletions
+14
View File
@@ -14,6 +14,20 @@
.. 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
+20 -1
View 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:
+59 -1
View 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):