From de9c030f5c09e0c1f3a61762dc2380662f8889ee Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sat, 7 May 2011 11:15:36 -0400 Subject: [PATCH] - Fixed the error message emitted for "can't execute syncrule for destination column 'q'; mapper 'X' does not map this column" to reference the correct mapper. [ticket:2163]. Also in 0.6.8. - test/orm/test_sync.py covers orm/sync.py 100% --- CHANGES | 6 + lib/sqlalchemy/orm/sync.py | 2 +- test/orm/test_sync.py | 223 ++++++++++++++++++++++++++++++++++ test/orm/test_unitofworkv2.py | 3 +- 4 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 test/orm/test_sync.py diff --git a/CHANGES b/CHANGES index faea97a02b..11ad730da0 100644 --- a/CHANGES +++ b/CHANGES @@ -48,6 +48,12 @@ CHANGES identity-map compatible with that of the primary mapper [ticket:2151] (also in 0.6.8) + - Fixed the error message emitted for "can't + execute syncrule for destination column 'q'; + mapper 'X' does not map this column" to + reference the correct mapper. [ticket:2163]. + Also in 0.6.8. + - sql - Some improvements to error handling inside of the execute procedure to ensure auto-close diff --git a/lib/sqlalchemy/orm/sync.py b/lib/sqlalchemy/orm/sync.py index 15d35d2a51..5ebd44fb97 100644 --- a/lib/sqlalchemy/orm/sync.py +++ b/lib/sqlalchemy/orm/sync.py @@ -95,7 +95,7 @@ def _raise_col_to_prop(isdest, source_mapper, source_column, dest_mapper, dest_c "Can't execute sync rule for destination column '%s'; " "mapper '%s' does not map this column. Try using an explicit" " `foreign_keys` collection which does not include this column " - "(or use a viewonly=True relation)." % (dest_column, source_mapper) + "(or use a viewonly=True relation)." % (dest_column, dest_mapper) ) else: raise exc.UnmappedColumnError( diff --git a/test/orm/test_sync.py b/test/orm/test_sync.py new file mode 100644 index 0000000000..178d396b94 --- /dev/null +++ b/test/orm/test_sync.py @@ -0,0 +1,223 @@ +from test.lib.testing import eq_, assert_raises, assert_raises_message +from test.lib import testing +from test.lib.schema import Table, Column +from test.orm import _fixtures +from test.lib import fixtures +from sqlalchemy import Integer, String, ForeignKey, func +from sqlalchemy.orm import mapper, relationship, backref, \ + create_session, unitofwork, attributes,\ + Session, class_mapper, sync, exc as orm_exc + + +class AssertsUOW(object): + def _get_test_uow(self, session): + uow = unitofwork.UOWTransaction(session) + deleted = set(session._deleted) + new = set(session._new) + dirty = set(session._dirty_states).difference(deleted) + for s in new.union(dirty): + uow.register_object(s) + for d in deleted: + uow.register_object(d, isdelete=True) + return uow + +class SyncTest(fixtures.MappedTest, + testing.AssertsExecutionResults, AssertsUOW): + + @classmethod + def define_tables(cls, metadata): + Table('t1', metadata, + Column('id', Integer, primary_key=True), + Column('foo', Integer) + ) + Table('t2', metadata, + Column('id', Integer, ForeignKey('t1.id'), primary_key=True), + Column('t1id', Integer, ForeignKey('t1.id')), + ) + + @classmethod + def setup_classes(cls): + class A(cls.Basic): + pass + class B(cls.Basic): + pass + + @classmethod + def setup_mappers(cls): + mapper(cls.classes.A, cls.tables.t1) + mapper(cls.classes.B, cls.tables.t2) + + def _fixture(self): + A, B = self.classes.A, self.classes.B + session = create_session() + uowcommit = self._get_test_uow(session) + a_mapper = class_mapper(A) + b_mapper= class_mapper(B) + self.a1 = a1 = A() + self.b1 = b1 = B() + uowcommit = self._get_test_uow(session) + return uowcommit,\ + attributes.instance_state(a1),\ + attributes.instance_state(b1),\ + a_mapper, b_mapper + + def test_populate(self): + uowcommit, a1, b1, a_mapper, b_mapper = self._fixture() + pairs = [(a_mapper.c.id, b_mapper.c.id)] + a1.obj().id = 7 + assert 'id' not in b1.obj().__dict__ + sync.populate(a1, a_mapper, b1, b_mapper, pairs, uowcommit, False) + eq_(b1.obj().id, 7) + eq_(b1.obj().__dict__['id'], 7) + assert ("pk_cascaded", b1, b_mapper.c.id) not in uowcommit.attributes + + def test_populate_flag_cascaded(self): + uowcommit, a1, b1, a_mapper, b_mapper = self._fixture() + pairs = [(a_mapper.c.id, b_mapper.c.id)] + a1.obj().id = 7 + assert 'id' not in b1.obj().__dict__ + sync.populate(a1, a_mapper, b1, b_mapper, pairs, uowcommit, True) + eq_(b1.obj().id, 7) + eq_(b1.obj().__dict__['id'], 7) + eq_(uowcommit.attributes[("pk_cascaded", b1, b_mapper.c.id)], True) + + def test_populate_unmapped_source(self): + uowcommit, a1, b1, a_mapper, b_mapper = self._fixture() + pairs = [(b_mapper.c.id, b_mapper.c.id)] + assert_raises_message( + orm_exc.UnmappedColumnError, + "Can't execute sync rule for source column 't2.id'; " + r"mapper 'Mapper\|A\|t1' does not map this column.", + sync.populate, + a1, + a_mapper, + b1, + b_mapper, + pairs, + uowcommit, False + ) + + def test_populate_unmapped_dest(self): + uowcommit, a1, b1, a_mapper, b_mapper = self._fixture() + pairs = [(a_mapper.c.id, a_mapper.c.id,)] + assert_raises_message( + orm_exc.UnmappedColumnError, + "Can't execute sync rule for destination " + r"column 't1.id'; mapper 'Mapper\|B\|t2' does not map this column.", + sync.populate, + a1, + a_mapper, + b1, + b_mapper, + pairs, + uowcommit, False + ) + + def test_clear(self): + uowcommit, a1, b1, a_mapper, b_mapper = self._fixture() + pairs = [(a_mapper.c.id, b_mapper.c.t1id,)] + b1.obj().t1id = 8 + eq_(b1.obj().__dict__['t1id'], 8) + sync.clear(b1, b_mapper, pairs) + eq_(b1.obj().__dict__['t1id'], None) + + def test_clear_pk(self): + uowcommit, a1, b1, a_mapper, b_mapper = self._fixture() + pairs = [(a_mapper.c.id, b_mapper.c.id,)] + b1.obj().id = 8 + eq_(b1.obj().__dict__['id'], 8) + assert_raises_message( + AssertionError, + "Dependency rule tried to blank-out primary key " + "column 't2.id' on instance '