diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-01-02 18:51:49 -0500 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-01-02 18:51:49 -0500 |
commit | 8a7fdd4e5cf5e4d9ba71c66a06bcba6b1054cfef (patch) | |
tree | e4ce11d8516235d6df37ca91ea40bf31f8e6edc1 | |
parent | ca8fca63916897f1bbc2fa4f1ee440c6b5d9a88a (diff) | |
download | sqlalchemy-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.rst | 14 | ||||
-rw-r--r-- | lib/sqlalchemy/ext/declarative/base.py | 21 | ||||
-rw-r--r-- | test/ext/declarative/test_basic.py | 60 |
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): |