Files
sqlalchemy/test/orm/test_backref_mutations.py
T
Khairi Hafsham 772374735d Make all tests to be PEP8 compliant
tested using pycodestyle version 2.2.0

Fixes: #3885
Change-Id: I5df43adc3aefe318f9eeab72a078247a548ec566
Pull-request: https://github.com/zzzeek/sqlalchemy/pull/343
2017-02-07 11:21:56 -05:00

742 lines
21 KiB
Python

"""
a series of tests which assert the behavior of moving objects between
collections and scalar attributes resulting in the expected state w.r.t.
backrefs, add/remove events, etc.
there's a particular focus on collections that have "uselist=False", since in
these cases the re-assignment of an attribute means the previous owner needs an
UPDATE in the database.
"""
from sqlalchemy.testing import assert_raises, assert_raises_message
from sqlalchemy import Integer, String, ForeignKey, Sequence, exc as sa_exc
from sqlalchemy.testing.schema import Table
from sqlalchemy.testing.schema import Column
from sqlalchemy.orm import mapper, relationship, create_session, \
class_mapper, backref, sessionmaker, Session
from sqlalchemy.orm import attributes, exc as orm_exc
from sqlalchemy import testing
from sqlalchemy.testing import eq_
from sqlalchemy.testing import fixtures
from test.orm import _fixtures
class O2MCollectionTest(_fixtures.FixtureTest):
run_inserts = None
@classmethod
def setup_mappers(cls):
Address, addresses, users, User = (cls.classes.Address,
cls.tables.addresses,
cls.tables.users,
cls.classes.User)
mapper(Address, addresses)
mapper(User, users, properties=dict(
addresses=relationship(Address, backref="user"),
))
def test_collection_move_hitslazy(self):
User, Address = self.classes.User, self.classes.Address
sess = sessionmaker()()
a1 = Address(email_address="address1")
a2 = Address(email_address="address2")
a3 = Address(email_address="address3")
u1 = User(name='jack', addresses=[a1, a2, a3])
u2 = User(name='ed')
sess.add_all([u1, a1, a2, a3])
sess.commit()
# u1.addresses
def go():
u2.addresses.append(a1)
u2.addresses.append(a2)
u2.addresses.append(a3)
self.assert_sql_count(testing.db, go, 0)
def test_collection_move_preloaded(self):
User, Address = self.classes.User, self.classes.Address
sess = sessionmaker()()
a1 = Address(email_address="address1")
u1 = User(name='jack', addresses=[a1])
u2 = User(name='ed')
sess.add_all([u1, u2])
sess.commit() # everything is expired
# load u1.addresses collection
u1.addresses
u2.addresses.append(a1)
# backref fires
assert a1.user is u2
# a1 removed from u1.addresses as of [ticket:2789]
assert a1 not in u1.addresses
assert a1 in u2.addresses
def test_collection_move_notloaded(self):
User, Address = self.classes.User, self.classes.Address
sess = sessionmaker()()
a1 = Address(email_address="address1")
u1 = User(name='jack', addresses=[a1])
u2 = User(name='ed')
sess.add_all([u1, u2])
sess.commit() # everything is expired
u2.addresses.append(a1)
# backref fires
assert a1.user is u2
# u1.addresses wasn't loaded,
# so when it loads its correct
assert a1 not in u1.addresses
assert a1 in u2.addresses
def test_collection_move_commitfirst(self):
User, Address = self.classes.User, self.classes.Address
sess = sessionmaker()()
a1 = Address(email_address="address1")
u1 = User(name='jack', addresses=[a1])
u2 = User(name='ed')
sess.add_all([u1, u2])
sess.commit() # everything is expired
# load u1.addresses collection
u1.addresses
u2.addresses.append(a1)
# backref fires
assert a1.user is u2
# everything expires, no changes in
# u1.addresses, so all is fine
sess.commit()
assert a1 not in u1.addresses
assert a1 in u2.addresses
def test_scalar_move_preloaded(self):
User, Address = self.classes.User, self.classes.Address
sess = sessionmaker()()
u1 = User(name='jack')
u2 = User(name='ed')
a1 = Address(email_address='a1')
a1.user = u1
sess.add_all([u1, u2, a1])
sess.commit()
# u1.addresses is loaded
u1.addresses
# direct set - the "old" is "fetched",
# but only from the local session - not the
# database, due to the PASSIVE_NO_FETCH flag.
# this is a more fine grained behavior introduced
# in 0.6
a1.user = u2
assert a1 not in u1.addresses
assert a1 in u2.addresses
def test_plain_load_passive(self):
"""test that many-to-one set doesn't load the old value."""
User, Address = self.classes.User, self.classes.Address
sess = sessionmaker()()
u1 = User(name='jack')
u2 = User(name='ed')
a1 = Address(email_address='a1')
a1.user = u1
sess.add_all([u1, u2, a1])
sess.commit()
# in this case, a lazyload would
# ordinarily occur except for the
# PASSIVE_NO_FETCH flag.
def go():
a1.user = u2
self.assert_sql_count(testing.db, go, 0)
assert a1 not in u1.addresses
assert a1 in u2.addresses
def test_set_none(self):
User, Address = self.classes.User, self.classes.Address
sess = sessionmaker()()
u1 = User(name='jack')
a1 = Address(email_address='a1')
a1.user = u1
sess.add_all([u1, a1])
sess.commit()
# works for None too
def go():
a1.user = None
self.assert_sql_count(testing.db, go, 0)
assert a1 not in u1.addresses
def test_scalar_move_notloaded(self):
User, Address = self.classes.User, self.classes.Address
sess = sessionmaker()()
u1 = User(name='jack')
u2 = User(name='ed')
a1 = Address(email_address='a1')
a1.user = u1
sess.add_all([u1, u2, a1])
sess.commit()
# direct set - the fetching of the
# "old" u1 here allows the backref
# to remove it from the addresses collection
a1.user = u2
assert a1 not in u1.addresses
assert a1 in u2.addresses
def test_scalar_move_commitfirst(self):
User, Address = self.classes.User, self.classes.Address
sess = sessionmaker()()
u1 = User(name='jack')
u2 = User(name='ed')
a1 = Address(email_address='a1')
a1.user = u1
sess.add_all([u1, u2, a1])
sess.commit()
# u1.addresses is loaded
u1.addresses
# direct set - the fetching of the
# "old" u1 here allows the backref
# to remove it from the addresses collection
a1.user = u2
sess.commit()
assert a1 not in u1.addresses
assert a1 in u2.addresses
class O2OScalarBackrefMoveTest(_fixtures.FixtureTest):
run_inserts = None
@classmethod
def setup_mappers(cls):
Address, addresses, users, User = (cls.classes.Address,
cls.tables.addresses,
cls.tables.users,
cls.classes.User)
mapper(Address, addresses)
mapper(User, users, properties={
'address': relationship(Address, backref=backref("user"),
uselist=False)
})
def test_collection_move_preloaded(self):
User, Address = self.classes.User, self.classes.Address
sess = sessionmaker()()
a1 = Address(email_address="address1")
u1 = User(name='jack', address=a1)
u2 = User(name='ed')
sess.add_all([u1, u2])
sess.commit() # everything is expired
# load u1.address
u1.address
# reassign
u2.address = a1
assert u2.address is a1
# backref fires
assert a1.user is u2
# doesn't extend to the previous attribute tho.
# flushing at this point means its anyone's guess.
assert u1.address is a1
assert u2.address is a1
def test_scalar_move_preloaded(self):
User, Address = self.classes.User, self.classes.Address
sess = sessionmaker()()
a1 = Address(email_address="address1")
a2 = Address(email_address="address1")
u1 = User(name='jack', address=a1)
sess.add_all([u1, a1, a2])
sess.commit() # everything is expired
# load a1.user
a1.user
# reassign
a2.user = u1
# backref fires
assert u1.address is a2
# stays on both sides
assert a1.user is u1
assert a2.user is u1
def test_collection_move_notloaded(self):
User, Address = self.classes.User, self.classes.Address
sess = sessionmaker()()
a1 = Address(email_address="address1")
u1 = User(name='jack', address=a1)
u2 = User(name='ed')
sess.add_all([u1, u2])
sess.commit() # everything is expired
# reassign
u2.address = a1
assert u2.address is a1
# backref fires
assert a1.user is u2
# u1.address loads now after a flush
assert u1.address is None
assert u2.address is a1
def test_scalar_move_notloaded(self):
User, Address = self.classes.User, self.classes.Address
sess = sessionmaker()()
a1 = Address(email_address="address1")
a2 = Address(email_address="address1")
u1 = User(name='jack', address=a1)
sess.add_all([u1, a1, a2])
sess.commit() # everything is expired
# reassign
a2.user = u1
# backref fires
assert u1.address is a2
# stays on both sides
assert a1.user is u1
assert a2.user is u1
def test_collection_move_commitfirst(self):
User, Address = self.classes.User, self.classes.Address
sess = sessionmaker()()
a1 = Address(email_address="address1")
u1 = User(name='jack', address=a1)
u2 = User(name='ed')
sess.add_all([u1, u2])
sess.commit() # everything is expired
# load u1.address
u1.address
# reassign
u2.address = a1
assert u2.address is a1
# backref fires
assert a1.user is u2
# the commit cancels out u1.addresses
# being loaded, on next access its fine.
sess.commit()
assert u1.address is None
assert u2.address is a1
def test_scalar_move_commitfirst(self):
User, Address = self.classes.User, self.classes.Address
sess = sessionmaker()()
a1 = Address(email_address="address1")
a2 = Address(email_address="address2")
u1 = User(name='jack', address=a1)
sess.add_all([u1, a1, a2])
sess.commit() # everything is expired
# load
assert a1.user is u1
# reassign
a2.user = u1
# backref fires
assert u1.address is a2
# didn't work this way tho
assert a1.user is u1
# moves appropriately after commit
sess.commit()
assert u1.address is a2
assert a1.user is None
assert a2.user is u1
class O2OScalarMoveTest(_fixtures.FixtureTest):
run_inserts = None
@classmethod
def setup_mappers(cls):
Address, addresses, users, User = (cls.classes.Address,
cls.tables.addresses,
cls.tables.users,
cls.classes.User)
mapper(Address, addresses)
mapper(User, users, properties={
'address': relationship(Address, uselist=False)
})
def test_collection_move_commitfirst(self):
User, Address = self.classes.User, self.classes.Address
sess = sessionmaker()()
a1 = Address(email_address="address1")
u1 = User(name='jack', address=a1)
u2 = User(name='ed')
sess.add_all([u1, u2])
sess.commit() # everything is expired
# load u1.address
u1.address
# reassign
u2.address = a1
assert u2.address is a1
# the commit cancels out u1.addresses
# being loaded, on next access its fine.
sess.commit()
assert u1.address is None
assert u2.address is a1
class O2OScalarOrphanTest(_fixtures.FixtureTest):
run_inserts = None
@classmethod
def setup_mappers(cls):
Address, addresses, users, User = (cls.classes.Address,
cls.tables.addresses,
cls.tables.users,
cls.classes.User)
mapper(Address, addresses)
mapper(User, users, properties={
'address': relationship(
Address, uselist=False,
backref=backref('user', single_parent=True,
cascade="all, delete-orphan"))
})
def test_m2o_event(self):
User, Address = self.classes.User, self.classes.Address
sess = sessionmaker()()
a1 = Address(email_address="address1")
u1 = User(name='jack', address=a1)
sess.add(u1)
sess.commit()
sess.expunge(u1)
u2 = User(name='ed')
# the _SingleParent extension sets the backref get to "active" !
# u1 gets loaded and deleted
u2.address = a1
sess.commit()
assert sess.query(User).count() == 1
class M2MCollectionMoveTest(_fixtures.FixtureTest):
run_inserts = None
@classmethod
def setup_mappers(cls):
keywords, items, item_keywords, \
Keyword, Item = (cls.tables.keywords,
cls.tables.items,
cls.tables.item_keywords,
cls.classes.Keyword,
cls.classes.Item)
mapper(Item, items, properties={
'keywords': relationship(Keyword, secondary=item_keywords,
backref='items')
})
mapper(Keyword, keywords)
def test_add_remove_pending_backref(self):
"""test that pending doesn't add an item that's not a net add."""
Item, Keyword = (self.classes.Item, self.classes.Keyword)
session = Session(autoflush=False)
i1 = Item(description='i1')
session.add(i1)
session.commit()
session.expire(i1, ['keywords'])
k1 = Keyword(name='k1')
k1.items.append(i1)
k1.items.remove(i1)
eq_(i1.keywords, [])
def test_remove_add_pending_backref(self):
"""test that pending doesn't remove an item that's not a net remove."""
Item, Keyword = (self.classes.Item, self.classes.Keyword)
session = Session(autoflush=False)
k1 = Keyword(name='k1')
i1 = Item(description='i1', keywords=[k1])
session.add(i1)
session.commit()
session.expire(i1, ['keywords'])
k1.items.remove(i1)
k1.items.append(i1)
eq_(i1.keywords, [k1])
def test_pending_combines_with_flushed(self):
"""test the combination of unflushed pending + lazy loaded from DB."""
Item, Keyword = (self.classes.Item, self.classes.Keyword)
session = Session(testing.db, autoflush=False)
k1 = Keyword(name='k1')
k2 = Keyword(name='k2')
i1 = Item(description='i1', keywords=[k1])
session.add(i1)
session.add(k2)
session.commit()
k2.items.append(i1)
# the pending
# list is still here.
eq_(
set(attributes.instance_state(i1).
_pending_mutations['keywords'].added_items),
set([k2])
)
# because autoflush is off, k2 is still
# coming in from pending
eq_(i1.keywords, [k1, k2])
# prove it didn't flush
eq_(session.scalar("select count(*) from item_keywords"), 1)
# the pending collection was removed
assert 'keywords' not in attributes.\
instance_state(i1).\
_pending_mutations
def test_duplicate_adds(self):
Item, Keyword = (self.classes.Item, self.classes.Keyword)
session = Session(testing.db, autoflush=False)
k1 = Keyword(name='k1')
i1 = Item(description='i1', keywords=[k1])
session.add(i1)
session.commit()
k1.items.append(i1)
eq_(i1.keywords, [k1, k1])
session.expire(i1, ['keywords'])
k1.items.append(i1)
eq_(i1.keywords, [k1, k1])
session.expire(i1, ['keywords'])
k1.items.append(i1)
eq_(i1.keywords, [k1, k1])
eq_(k1.items, [i1, i1, i1, i1])
session.commit()
eq_(k1.items, [i1])
class M2MScalarMoveTest(_fixtures.FixtureTest):
run_inserts = None
@classmethod
def setup_mappers(cls):
keywords, items, item_keywords, \
Keyword, Item = (cls.tables.keywords,
cls.tables.items,
cls.tables.item_keywords,
cls.classes.Keyword,
cls.classes.Item)
mapper(Item, items, properties={
'keyword': relationship(Keyword, secondary=item_keywords,
uselist=False,
backref=backref("item", uselist=False))
})
mapper(Keyword, keywords)
def test_collection_move_preloaded(self):
Item, Keyword = self.classes.Item, self.classes.Keyword
sess = sessionmaker()()
k1 = Keyword(name='k1')
i1 = Item(description='i1', keyword=k1)
i2 = Item(description='i2')
sess.add_all([i1, i2, k1])
sess.commit() # everything is expired
# load i1.keyword
assert i1.keyword is k1
i2.keyword = k1
assert k1.item is i2
# nothing happens.
assert i1.keyword is k1
assert i2.keyword is k1
def test_collection_move_notloaded(self):
Item, Keyword = self.classes.Item, self.classes.Keyword
sess = sessionmaker()()
k1 = Keyword(name='k1')
i1 = Item(description='i1', keyword=k1)
i2 = Item(description='i2')
sess.add_all([i1, i2, k1])
sess.commit() # everything is expired
i2.keyword = k1
assert k1.item is i2
assert i1.keyword is None
assert i2.keyword is k1
def test_collection_move_commit(self):
Item, Keyword = self.classes.Item, self.classes.Keyword
sess = sessionmaker()()
k1 = Keyword(name='k1')
i1 = Item(description='i1', keyword=k1)
i2 = Item(description='i2')
sess.add_all([i1, i2, k1])
sess.commit() # everything is expired
# load i1.keyword
assert i1.keyword is k1
i2.keyword = k1
assert k1.item is i2
sess.commit()
assert i1.keyword is None
assert i2.keyword is k1
class O2MStaleBackrefTest(_fixtures.FixtureTest):
run_inserts = None
@classmethod
def setup_mappers(cls):
Address, addresses, users, User = (cls.classes.Address,
cls.tables.addresses,
cls.tables.users,
cls.classes.User)
mapper(Address, addresses)
mapper(User, users, properties=dict(
addresses=relationship(Address, backref="user"),
))
def test_backref_pop_m2o(self):
User, Address = self.classes.User, self.classes.Address
u1 = User()
u2 = User()
a1 = Address()
u1.addresses.append(a1)
u2.addresses.append(a1)
# a1 removed from u1.addresses as of [ticket:2789]
assert a1 not in u1.addresses
assert a1.user is u2
assert a1 in u2.addresses
class M2MStaleBackrefTest(_fixtures.FixtureTest):
run_inserts = None
@classmethod
def setup_mappers(cls):
keywords, items, item_keywords, \
Keyword, Item = (cls.tables.keywords,
cls.tables.items,
cls.tables.item_keywords,
cls.classes.Keyword,
cls.classes.Item)
mapper(Item, items, properties={
'keywords': relationship(Keyword, secondary=item_keywords,
backref='items')
})
mapper(Keyword, keywords)
def test_backref_pop_m2m(self):
Keyword, Item = self.classes.Keyword, self.classes.Item
k1 = Keyword()
k2 = Keyword()
i1 = Item()
k1.items.append(i1)
k2.items.append(i1)
k2.items.append(i1)
i1.keywords = []
k2.items.remove(i1)
assert len(k2.items) == 0