1
from test.lib.testing import eq_, assert_raises, assert_raises_message
2
from test.lib import testing, engines
3
from test.lib.schema import Table, Column
1
from sqlalchemy.testing import eq_, assert_raises, assert_raises_message
2
from sqlalchemy import testing
3
from sqlalchemy.testing import engines
4
from sqlalchemy.testing.schema import Table, Column
4
5
from test.orm import _fixtures
5
from test.lib import fixtures
6
from sqlalchemy.testing import fixtures
6
7
from sqlalchemy import Integer, String, ForeignKey, func
7
8
from sqlalchemy.orm import mapper, relationship, backref, \
8
9
create_session, unitofwork, attributes,\
9
10
Session, class_mapper, sync, exc as orm_exc
11
from test.lib.assertsql import AllOf, CompiledSQL
12
from sqlalchemy.testing.assertsql import AllOf, CompiledSQL
13
14
class AssertsUOW(object):
14
15
def _get_test_uow(self, session):
1326
class LoadersUsingCommittedTest(UOWTest):
1327
"""Test that events which occur within a flush()
1328
get the same attribute loading behavior as on the outside
1329
of the flush, and that the unit of work itself uses the
1330
"committed" version of primary/foreign key attributes
1331
when loading a collection for historical purposes (this typically
1332
has importance for when primary key values change).
1336
def _mapper_setup(self, passive_updates=True):
1337
users, Address, addresses, User = (self.tables.users,
1338
self.classes.Address,
1339
self.tables.addresses,
1342
mapper(User, users, properties={
1343
'addresses': relationship(Address,
1344
order_by=addresses.c.email_address,
1345
passive_updates=passive_updates,
1348
mapper(Address, addresses)
1349
return create_session(autocommit=False)
1351
def test_before_update_m2o(self):
1352
"""Expect normal many to one attribute load behavior
1353
(should not get committed value)
1354
from within public 'before_update' event"""
1355
sess = self._mapper_setup()
1357
Address, User = self.classes.Address, self.classes.User
1359
def before_update(mapper, connection, target):
1360
# if get committed is used to find target.user, then
1361
# it will be still be u1 instead of u2
1362
assert target.user.id == target.user_id == u2.id
1363
from sqlalchemy import event
1364
event.listen(Address, 'before_update', before_update)
1366
a1 = Address(email_address='a1')
1367
u1 = User(name='u1', addresses=[a1])
1370
u2 = User(name='u2')
1375
# lookup an address and move it to the other user
1376
a1 = sess.query(Address).get(a1.id)
1378
# move address to another user's fk
1379
assert a1.user_id == u1.id
1384
def test_before_update_o2m_passive(self):
1385
"""Expect normal one to many attribute load behavior
1386
(should not get committed value)
1387
from within public 'before_update' event"""
1388
self._test_before_update_o2m(True)
1390
def test_before_update_o2m_notpassive(self):
1391
"""Expect normal one to many attribute load behavior
1392
(should not get committed value)
1393
from within public 'before_update' event with
1394
passive_updates=False
1397
self._test_before_update_o2m(False)
1399
def _test_before_update_o2m(self, passive_updates):
1400
sess = self._mapper_setup(passive_updates=passive_updates)
1402
Address, User = self.classes.Address, self.classes.User
1404
class AvoidReferencialError(Exception):
1405
"""the test here would require ON UPDATE CASCADE on FKs
1406
for the flush to fully succeed; this exception is used
1407
to cancel the flush before we get that far.
1411
def before_update(mapper, connection, target):
1413
# we shouldn't be using committed value.
1414
# so, having switched target's primary key,
1415
# we expect no related items in the collection
1416
# since we are using passive_updates
1417
# this is a behavior change since #2350
1418
assert 'addresses' not in target.__dict__
1419
eq_(target.addresses, [])
1421
# in contrast with passive_updates=True,
1422
# here we expect the orm to have looked up the addresses
1423
# with the committed value (it needs to in order to
1424
# update the foreign keys). So we expect addresses
1425
# collection to move with the user,
1426
# (just like they will be after the update)
1428
# collection is already loaded
1429
assert 'addresses' in target.__dict__
1430
eq_([a.id for a in target.addresses],
1431
[a.id for a in [a1, a2]])
1432
raise AvoidReferencialError()
1433
from sqlalchemy import event
1434
event.listen(User, 'before_update', before_update)
1436
a1 = Address(email_address='jack1')
1437
a2 = Address(email_address='jack2')
1438
u1 = User(id=1, name='jack', addresses=[a1, a2])
1443
u1 = sess.query(User).get(u1.id)
1447
except AvoidReferencialError: