From: Richard Mitchell Date: Tue, 27 Nov 2012 14:03:42 +0000 (+0000) Subject: Allow use of synonyms in primaryjoin / secondaryjoin conditions X-Git-Tag: rel_0_8_0b2~23 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=92535b4b575874a6c0f0392cab46300fe59107ee;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Allow use of synonyms in primaryjoin / secondaryjoin conditions --- diff --git a/doc/build/changelog/changelog_08.rst b/doc/build/changelog/changelog_08.rst index 45a612b491..1cad79e116 100644 --- a/doc/build/changelog/changelog_08.rst +++ b/doc/build/changelog/changelog_08.rst @@ -6,6 +6,12 @@ .. changelog:: :version: 0.8.0b2 + .. change:: + :tags: orm, feature + + Allow synonyms to be used when defining primary and secondary + joins for relationships. + .. change:: :tags: orm, bug :ticket: 2614 @@ -1533,4 +1539,4 @@ :tags: sql, removed The long-deprecated and non-functional ``assert_unicode`` flag on - :func:`.create_engine` as well as :class:`.String` is removed. \ No newline at end of file + :func:`.create_engine` as well as :class:`.String` is removed. diff --git a/lib/sqlalchemy/ext/declarative/clsregistry.py b/lib/sqlalchemy/ext/declarative/clsregistry.py index a0e177f773..0ae10b25a7 100644 --- a/lib/sqlalchemy/ext/declarative/clsregistry.py +++ b/lib/sqlalchemy/ext/declarative/clsregistry.py @@ -9,7 +9,8 @@ This system allows specification of classes and expressions used in :func:`.relationship` using strings. """ -from ...orm.properties import ColumnProperty, RelationshipProperty +from ...orm.properties import ColumnProperty, RelationshipProperty, \ + SynonymProperty from ...schema import _get_table_key from ...orm import class_mapper from ... import util @@ -195,7 +196,9 @@ class _GetColumns(object): % (self.cls, key)) prop = mp.get_property(key) - if not isinstance(prop, ColumnProperty): + if isinstance(prop, SynonymProperty): + key = prop.name + elif not isinstance(prop, ColumnProperty): raise exc.InvalidRequestError( "Property %r is not an instance of" " ColumnProperty (i.e. does not correspond" diff --git a/test/ext/declarative/test_basic.py b/test/ext/declarative/test_basic.py index 19141143f8..0fe54a1549 100644 --- a/test/ext/declarative/test_basic.py +++ b/test/ext/declarative/test_basic.py @@ -14,7 +14,8 @@ from sqlalchemy.orm import relationship, create_session, class_mapper, \ Session from sqlalchemy.testing import eq_ from sqlalchemy.util import classproperty -from sqlalchemy.ext.declarative import declared_attr, AbstractConcreteBase, ConcreteBase +from sqlalchemy.ext.declarative import declared_attr, AbstractConcreteBase, \ + ConcreteBase, synonym_for from sqlalchemy.testing import fixtures from sqlalchemy.testing.util import gc_collect @@ -251,6 +252,44 @@ class DeclarativeTest(DeclarativeTestBase): "'addresses' is not an instance of " "ColumnProperty", configure_mappers) + def test_string_dependency_resolution_synonym(self): + from sqlalchemy.sql import desc + + class User(Base, fixtures.ComparableEntity): + + __tablename__ = 'users' + id = Column(Integer, primary_key=True, + test_needs_autoincrement=True) + name = Column(String(50)) + + Base.metadata.create_all() + sess = create_session() + u1 = User(name='ed') + sess.add(u1) + sess.flush() + sess.expunge_all() + eq_(sess.query(User).filter(User.name == 'ed').one(), + User(name='ed')) + + class Foo(Base, fixtures.ComparableEntity): + + __tablename__ = 'foo' + id = Column(Integer, primary_key=True) + _user_id = Column(Integer) + rel = relationship('User', + uselist=False, + foreign_keys=[User.id], + primaryjoin='Foo.user_id==User.id') + + @synonym_for('_user_id') + @property + def user_id(self): + return self._user_id + + foo = Foo() + foo.rel = u1 + assert foo.rel == u1 + def test_string_dependency_resolution_two(self): class User(Base, fixtures.ComparableEntity): diff --git a/test/orm/test_relationships.py b/test/orm/test_relationships.py index 6026831b52..3ecd273aa6 100644 --- a/test/orm/test_relationships.py +++ b/test/orm/test_relationships.py @@ -8,7 +8,7 @@ from sqlalchemy.orm import mapper, relationship, relation, \ backref, create_session, configure_mappers, \ clear_mappers, sessionmaker, attributes,\ Session, composite, column_property, foreign,\ - remote + remote, synonym from sqlalchemy.orm.interfaces import ONETOMANY, MANYTOONE, MANYTOMANY from sqlalchemy.testing import eq_, startswith_, AssertsCompiledSQL, is_ from sqlalchemy.testing import fixtures @@ -514,6 +514,62 @@ class CompositeJoinPartialFK(fixtures.MappedTest, AssertsCompiledSQL): ) +class SynonymsAsFKsTest(fixtures.MappedTest): + """Syncrules on foreign keys that are also primary""" + + @classmethod + def define_tables(cls, metadata): + Table("tableA", metadata, + Column("id",Integer,primary_key=True, + test_needs_autoincrement=True), + Column("foo",Integer,), + test_needs_fk=True) + + Table("tableB",metadata, + Column("id",Integer,primary_key=True, + test_needs_autoincrement=True), + Column("_a_id", Integer, key='a_id', primary_key=True), + test_needs_fk=True) + + @classmethod + def setup_classes(cls): + class A(cls.Basic): + pass + + class B(cls.Basic): + @property + def a_id(self): + return self._a_id + + def test_synonym_fk(self): + """test that active history is enabled on a + one-to-many/one that has use_get==True""" + + tableB, A, B, tableA = (self.tables.tableB, + self.classes.A, + self.classes.B, + self.tables.tableA) + + mapper(B, tableB, properties={ + 'a_id': synonym('_a_id', map_column=True)}) + mapper(A, tableA, properties={ + 'b': relationship(B, primaryjoin=(tableA.c.id == foreign(B.a_id)), + uselist=False)}) + + sess = create_session() + + b = B(id=0) + a = A(id=0, b=b) + sess.add(a) + sess.add(b) + sess.flush() + sess.expunge_all() + + assert a.b == b + assert a.id == b.a_id + assert a.id == b._a_id + + class FKsAsPksTest(fixtures.MappedTest): """Syncrules on foreign keys that are also primary"""