summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2014-01-02 18:51:49 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2014-01-02 18:51:49 -0500
commit8a7fdd4e5cf5e4d9ba71c66a06bcba6b1054cfef (patch)
treee4ce11d8516235d6df37ca91ea40bf31f8e6edc1
parentca8fca63916897f1bbc2fa4f1ee440c6b5d9a88a (diff)
downloadsqlalchemy-8a7fdd4e5cf5e4d9ba71c66a06bcba6b1054cfef.tar.gz
- 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. [ticket:2900]
-rw-r--r--doc/build/changelog/changelog_09.rst14
-rw-r--r--lib/sqlalchemy/ext/declarative/base.py21
-rw-r--r--test/ext/declarative/test_basic.py60
3 files changed, 93 insertions, 2 deletions
diff --git a/doc/build/changelog/changelog_09.rst b/doc/build/changelog/changelog_09.rst
index 069df37a4..783c674fd 100644
--- a/doc/build/changelog/changelog_09.rst
+++ b/doc/build/changelog/changelog_09.rst
@@ -15,6 +15,20 @@
: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
diff --git a/lib/sqlalchemy/ext/declarative/base.py b/lib/sqlalchemy/ext/declarative/base.py
index f7668a540..69e4b9eea 100644
--- a/lib/sqlalchemy/ext/declarative/base.py
+++ b/lib/sqlalchemy/ext/declarative/base.py
@@ -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:
diff --git a/test/ext/declarative/test_basic.py b/test/ext/declarative/test_basic.py
index 0d213fce3..1f14d8164 100644
--- a/test/ext/declarative/test_basic.py
+++ b/test/ext/declarative/test_basic.py
@@ -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):