mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-05-29 12:06:28 -04:00
4ece86eb41
The state of the :class:`.Session` is now present when the :meth:`.SessionEvents.after_rollback` event is emitted, that is, the attribute state of objects prior to their being expired. This is now consistent with the behavior of the :meth:`.SessionEvents.after_commit` event which also emits before the attribute state of objects is expired. Change-Id: I9c572656ec5a9bfaeab817e9c95107c75aca1b51 Fixes: #3934
2737 lines
77 KiB
Python
2737 lines
77 KiB
Python
from sqlalchemy.testing import assert_raises_message, assert_raises
|
|
import sqlalchemy as sa
|
|
from sqlalchemy import testing
|
|
from sqlalchemy import Integer, String
|
|
from sqlalchemy.testing.schema import Table, Column
|
|
from sqlalchemy.orm import mapper, relationship, \
|
|
create_session, class_mapper, \
|
|
Mapper, column_property, query, \
|
|
Session, sessionmaker, attributes, configure_mappers
|
|
from sqlalchemy.orm.instrumentation import ClassManager
|
|
from sqlalchemy.orm import instrumentation, events
|
|
from sqlalchemy.testing import eq_, is_not_
|
|
from sqlalchemy.testing import fixtures
|
|
from sqlalchemy.testing import AssertsCompiledSQL
|
|
from sqlalchemy.testing.util import gc_collect
|
|
from test.orm import _fixtures
|
|
from sqlalchemy import event
|
|
from sqlalchemy.testing.mock import Mock, call, ANY
|
|
|
|
|
|
class _RemoveListeners(object):
|
|
|
|
def teardown(self):
|
|
events.MapperEvents._clear()
|
|
events.InstanceEvents._clear()
|
|
events.SessionEvents._clear()
|
|
events.InstrumentationEvents._clear()
|
|
events.QueryEvents._clear()
|
|
super(_RemoveListeners, self).teardown()
|
|
|
|
|
|
class MapperEventsTest(_RemoveListeners, _fixtures.FixtureTest):
|
|
run_inserts = None
|
|
|
|
@classmethod
|
|
def define_tables(cls, metadata):
|
|
super(MapperEventsTest, cls).define_tables(metadata)
|
|
metadata.tables['users'].append_column(
|
|
Column('extra', Integer, default=5, onupdate=10)
|
|
)
|
|
|
|
def test_instance_event_listen(self):
|
|
"""test listen targets for instance events"""
|
|
|
|
users, addresses = self.tables.users, self.tables.addresses
|
|
|
|
canary = []
|
|
|
|
class A(object):
|
|
pass
|
|
|
|
class B(A):
|
|
pass
|
|
|
|
mapper(A, users)
|
|
mapper(B, addresses, inherits=A,
|
|
properties={'address_id': addresses.c.id})
|
|
|
|
def init_a(target, args, kwargs):
|
|
canary.append(('init_a', target))
|
|
|
|
def init_b(target, args, kwargs):
|
|
canary.append(('init_b', target))
|
|
|
|
def init_c(target, args, kwargs):
|
|
canary.append(('init_c', target))
|
|
|
|
def init_d(target, args, kwargs):
|
|
canary.append(('init_d', target))
|
|
|
|
def init_e(target, args, kwargs):
|
|
canary.append(('init_e', target))
|
|
|
|
event.listen(mapper, 'init', init_a)
|
|
event.listen(Mapper, 'init', init_b)
|
|
event.listen(class_mapper(A), 'init', init_c)
|
|
event.listen(A, 'init', init_d)
|
|
event.listen(A, 'init', init_e, propagate=True)
|
|
|
|
a = A()
|
|
eq_(canary, [('init_a', a), ('init_b', a),
|
|
('init_c', a), ('init_d', a), ('init_e', a)])
|
|
|
|
# test propagate flag
|
|
canary[:] = []
|
|
b = B()
|
|
eq_(canary, [('init_a', b), ('init_b', b), ('init_e', b)])
|
|
|
|
def listen_all(self, mapper, **kw):
|
|
canary = []
|
|
|
|
def evt(meth):
|
|
def go(*args, **kwargs):
|
|
canary.append(meth)
|
|
return go
|
|
|
|
for meth in [
|
|
'init',
|
|
'init_failure',
|
|
'load',
|
|
'refresh',
|
|
'refresh_flush',
|
|
'expire',
|
|
'before_insert',
|
|
'after_insert',
|
|
'before_update',
|
|
'after_update',
|
|
'before_delete',
|
|
'after_delete'
|
|
]:
|
|
event.listen(mapper, meth, evt(meth), **kw)
|
|
return canary
|
|
|
|
def test_init_allow_kw_modify(self):
|
|
User, users = self.classes.User, self.tables.users
|
|
mapper(User, users)
|
|
|
|
@event.listens_for(User, 'init')
|
|
def add_name(obj, args, kwargs):
|
|
kwargs['name'] = 'ed'
|
|
|
|
u1 = User()
|
|
eq_(u1.name, 'ed')
|
|
|
|
def test_init_failure_hook(self):
|
|
users = self.tables.users
|
|
|
|
class Thing(object):
|
|
def __init__(self, **kw):
|
|
if kw.get('fail'):
|
|
raise Exception("failure")
|
|
|
|
mapper(Thing, users)
|
|
|
|
canary = Mock()
|
|
event.listen(Thing, 'init_failure', canary)
|
|
|
|
Thing()
|
|
eq_(canary.mock_calls, [])
|
|
|
|
assert_raises_message(
|
|
Exception,
|
|
"failure",
|
|
Thing, fail=True
|
|
)
|
|
eq_(
|
|
canary.mock_calls,
|
|
[call(ANY, (), {'fail': True})]
|
|
)
|
|
|
|
def test_listen_doesnt_force_compile(self):
|
|
User, users = self.classes.User, self.tables.users
|
|
m = mapper(User, users, properties={
|
|
'addresses': relationship(lambda: ImNotAClass)
|
|
})
|
|
event.listen(User, "before_insert", lambda *a, **kw: None)
|
|
assert not m.configured
|
|
|
|
def test_basic(self):
|
|
User, users = self.classes.User, self.tables.users
|
|
|
|
mapper(User, users)
|
|
canary = self.listen_all(User)
|
|
named_canary = self.listen_all(User, named=True)
|
|
|
|
sess = create_session()
|
|
u = User(name='u1')
|
|
sess.add(u)
|
|
sess.flush()
|
|
sess.expire(u)
|
|
u = sess.query(User).get(u.id)
|
|
sess.expunge_all()
|
|
u = sess.query(User).get(u.id)
|
|
u.name = 'u1 changed'
|
|
sess.flush()
|
|
sess.delete(u)
|
|
sess.flush()
|
|
expected = [
|
|
'init', 'before_insert',
|
|
'refresh_flush',
|
|
'after_insert', 'expire',
|
|
'refresh',
|
|
'load',
|
|
'before_update', 'refresh_flush', 'after_update', 'before_delete',
|
|
'after_delete']
|
|
eq_(canary, expected)
|
|
eq_(named_canary, expected)
|
|
|
|
def test_insert_before_configured(self):
|
|
users, User = self.tables.users, self.classes.User
|
|
|
|
mapper(User, users)
|
|
|
|
canary = Mock()
|
|
|
|
event.listen(mapper, "before_configured", canary.listen1)
|
|
event.listen(mapper, "before_configured", canary.listen2, insert=True)
|
|
event.listen(mapper, "before_configured", canary.listen3)
|
|
event.listen(mapper, "before_configured", canary.listen4, insert=True)
|
|
|
|
configure_mappers()
|
|
|
|
eq_(
|
|
canary.mock_calls,
|
|
[call.listen4(), call.listen2(), call.listen1(), call.listen3()]
|
|
)
|
|
|
|
def test_insert_flags(self):
|
|
users, User = self.tables.users, self.classes.User
|
|
|
|
m = mapper(User, users)
|
|
|
|
canary = Mock()
|
|
|
|
arg = Mock()
|
|
|
|
event.listen(m, "before_insert", canary.listen1, )
|
|
event.listen(m, "before_insert", canary.listen2, insert=True)
|
|
event.listen(m, "before_insert", canary.listen3,
|
|
propagate=True, insert=True)
|
|
event.listen(m, "load", canary.listen4)
|
|
event.listen(m, "load", canary.listen5, insert=True)
|
|
event.listen(m, "load", canary.listen6, propagate=True, insert=True)
|
|
|
|
u1 = User()
|
|
state = u1._sa_instance_state
|
|
m.dispatch.before_insert(arg, arg, arg)
|
|
m.class_manager.dispatch.load(arg, arg)
|
|
eq_(
|
|
canary.mock_calls,
|
|
[
|
|
call.listen3(arg, arg, arg.obj()),
|
|
call.listen2(arg, arg, arg.obj()),
|
|
call.listen1(arg, arg, arg.obj()),
|
|
call.listen6(arg.obj(), arg),
|
|
call.listen5(arg.obj(), arg),
|
|
call.listen4(arg.obj(), arg)
|
|
]
|
|
)
|
|
|
|
def test_merge(self):
|
|
users, User = self.tables.users, self.classes.User
|
|
|
|
mapper(User, users)
|
|
|
|
canary = []
|
|
|
|
def load(obj, ctx):
|
|
canary.append('load')
|
|
event.listen(mapper, 'load', load)
|
|
|
|
s = Session()
|
|
u = User(name='u1')
|
|
s.add(u)
|
|
s.commit()
|
|
s = Session()
|
|
u2 = s.merge(u)
|
|
s = Session()
|
|
u2 = s.merge(User(name='u2')) # noqa
|
|
s.commit()
|
|
s.query(User).order_by(User.id).first()
|
|
eq_(canary, ['load', 'load', 'load'])
|
|
|
|
def test_inheritance(self):
|
|
users, addresses, User = (self.tables.users,
|
|
self.tables.addresses,
|
|
self.classes.User)
|
|
|
|
class AdminUser(User):
|
|
pass
|
|
|
|
mapper(User, users)
|
|
mapper(AdminUser, addresses, inherits=User,
|
|
properties={'address_id': addresses.c.id})
|
|
|
|
canary1 = self.listen_all(User, propagate=True)
|
|
canary2 = self.listen_all(User)
|
|
canary3 = self.listen_all(AdminUser)
|
|
|
|
sess = create_session()
|
|
am = AdminUser(name='au1', email_address='au1@e1')
|
|
sess.add(am)
|
|
sess.flush()
|
|
am = sess.query(AdminUser).populate_existing().get(am.id)
|
|
sess.expunge_all()
|
|
am = sess.query(AdminUser).get(am.id)
|
|
am.name = 'au1 changed'
|
|
sess.flush()
|
|
sess.delete(am)
|
|
sess.flush()
|
|
eq_(canary1, ['init', 'before_insert', 'refresh_flush', 'after_insert',
|
|
'refresh', 'load',
|
|
'before_update', 'refresh_flush',
|
|
'after_update', 'before_delete',
|
|
'after_delete'])
|
|
eq_(canary2, [])
|
|
eq_(canary3, ['init', 'before_insert', 'refresh_flush', 'after_insert',
|
|
'refresh',
|
|
'load',
|
|
'before_update', 'refresh_flush',
|
|
'after_update', 'before_delete',
|
|
'after_delete'])
|
|
|
|
def test_inheritance_subclass_deferred(self):
|
|
users, addresses, User = (self.tables.users,
|
|
self.tables.addresses,
|
|
self.classes.User)
|
|
|
|
mapper(User, users)
|
|
|
|
canary1 = self.listen_all(User, propagate=True)
|
|
canary2 = self.listen_all(User)
|
|
|
|
class AdminUser(User):
|
|
pass
|
|
mapper(AdminUser, addresses, inherits=User,
|
|
properties={'address_id': addresses.c.id})
|
|
canary3 = self.listen_all(AdminUser)
|
|
|
|
sess = create_session()
|
|
am = AdminUser(name='au1', email_address='au1@e1')
|
|
sess.add(am)
|
|
sess.flush()
|
|
am = sess.query(AdminUser).populate_existing().get(am.id)
|
|
sess.expunge_all()
|
|
am = sess.query(AdminUser).get(am.id)
|
|
am.name = 'au1 changed'
|
|
sess.flush()
|
|
sess.delete(am)
|
|
sess.flush()
|
|
eq_(canary1, ['init', 'before_insert', 'refresh_flush', 'after_insert',
|
|
'refresh', 'load',
|
|
'before_update', 'refresh_flush',
|
|
'after_update', 'before_delete',
|
|
'after_delete'])
|
|
eq_(canary2, [])
|
|
eq_(canary3, ['init', 'before_insert', 'refresh_flush', 'after_insert',
|
|
'refresh', 'load',
|
|
'before_update', 'refresh_flush',
|
|
'after_update', 'before_delete',
|
|
'after_delete'])
|
|
|
|
def test_before_after_only_collection(self):
|
|
"""before_update is called on parent for collection modifications,
|
|
after_update is called even if no columns were updated.
|
|
|
|
"""
|
|
|
|
keywords, items, item_keywords, Keyword, Item = (
|
|
self.tables.keywords,
|
|
self.tables.items,
|
|
self.tables.item_keywords,
|
|
self.classes.Keyword,
|
|
self.classes.Item)
|
|
|
|
mapper(Item, items, properties={
|
|
'keywords': relationship(Keyword, secondary=item_keywords)})
|
|
mapper(Keyword, keywords)
|
|
|
|
canary1 = self.listen_all(Item)
|
|
canary2 = self.listen_all(Keyword)
|
|
|
|
sess = create_session()
|
|
i1 = Item(description="i1")
|
|
k1 = Keyword(name="k1")
|
|
sess.add(i1)
|
|
sess.add(k1)
|
|
sess.flush()
|
|
eq_(canary1,
|
|
['init',
|
|
'before_insert', 'after_insert'])
|
|
eq_(canary2,
|
|
['init',
|
|
'before_insert', 'after_insert'])
|
|
|
|
canary1[:] = []
|
|
canary2[:] = []
|
|
|
|
i1.keywords.append(k1)
|
|
sess.flush()
|
|
eq_(canary1, ['before_update', 'after_update'])
|
|
eq_(canary2, [])
|
|
|
|
def test_before_after_configured_warn_on_non_mapper(self):
|
|
User, users = self.classes.User, self.tables.users
|
|
|
|
m1 = Mock()
|
|
|
|
mapper(User, users)
|
|
assert_raises_message(
|
|
sa.exc.SAWarning,
|
|
r"before_configured' and 'after_configured' ORM events only "
|
|
r"invoke with the mapper\(\) function or Mapper class as "
|
|
r"the target.",
|
|
event.listen, User, 'before_configured', m1
|
|
)
|
|
|
|
assert_raises_message(
|
|
sa.exc.SAWarning,
|
|
r"before_configured' and 'after_configured' ORM events only "
|
|
r"invoke with the mapper\(\) function or Mapper class as "
|
|
r"the target.",
|
|
event.listen, User, 'after_configured', m1
|
|
)
|
|
|
|
def test_before_after_configured(self):
|
|
User, users = self.classes.User, self.tables.users
|
|
|
|
m1 = Mock()
|
|
m2 = Mock()
|
|
|
|
mapper(User, users)
|
|
|
|
event.listen(mapper, "before_configured", m1)
|
|
event.listen(mapper, "after_configured", m2)
|
|
|
|
s = Session()
|
|
s.query(User)
|
|
|
|
eq_(m1.mock_calls, [call()])
|
|
eq_(m2.mock_calls, [call()])
|
|
|
|
def test_instrument_event(self):
|
|
Address, addresses, users, User = (self.classes.Address,
|
|
self.tables.addresses,
|
|
self.tables.users,
|
|
self.classes.User)
|
|
|
|
canary = []
|
|
|
|
def instrument_class(mapper, cls):
|
|
canary.append(cls)
|
|
|
|
event.listen(Mapper, 'instrument_class', instrument_class)
|
|
|
|
mapper(User, users)
|
|
eq_(canary, [User])
|
|
mapper(Address, addresses)
|
|
eq_(canary, [User, Address])
|
|
|
|
def test_instrument_class_precedes_class_instrumentation(self):
|
|
users = self.tables.users
|
|
|
|
class MyClass(object):
|
|
pass
|
|
|
|
canary = Mock()
|
|
|
|
def my_init(self):
|
|
canary.init()
|
|
|
|
# mapper level event
|
|
@event.listens_for(mapper, "instrument_class")
|
|
def instrument_class(mp, class_):
|
|
canary.instrument_class(class_)
|
|
class_.__init__ = my_init
|
|
|
|
# instrumentationmanager event
|
|
@event.listens_for(object, "class_instrument")
|
|
def class_instrument(class_):
|
|
canary.class_instrument(class_)
|
|
|
|
mapper(MyClass, users)
|
|
|
|
m1 = MyClass()
|
|
assert attributes.instance_state(m1)
|
|
|
|
eq_(
|
|
[
|
|
call.instrument_class(MyClass),
|
|
call.class_instrument(MyClass),
|
|
call.init()
|
|
],
|
|
canary.mock_calls
|
|
)
|
|
|
|
|
|
class DeclarativeEventListenTest(_RemoveListeners,
|
|
fixtures.DeclarativeMappedTest):
|
|
run_setup_classes = "each"
|
|
run_deletes = None
|
|
|
|
def test_inheritance_propagate_after_config(self):
|
|
# test [ticket:2949]
|
|
|
|
class A(self.DeclarativeBasic):
|
|
__tablename__ = 'a'
|
|
id = Column(Integer, primary_key=True)
|
|
|
|
class B(A):
|
|
pass
|
|
|
|
listen = Mock()
|
|
event.listen(self.DeclarativeBasic, "load", listen, propagate=True)
|
|
|
|
class C(B):
|
|
pass
|
|
|
|
m1 = A.__mapper__.class_manager
|
|
m2 = B.__mapper__.class_manager
|
|
m3 = C.__mapper__.class_manager
|
|
a1 = A()
|
|
b1 = B()
|
|
c1 = C()
|
|
m3.dispatch.load(c1._sa_instance_state, "c")
|
|
m2.dispatch.load(b1._sa_instance_state, "b")
|
|
m1.dispatch.load(a1._sa_instance_state, "a")
|
|
eq_(
|
|
listen.mock_calls,
|
|
[call(c1, "c"), call(b1, "b"), call(a1, "a")]
|
|
)
|
|
|
|
|
|
class DeferredMapperEventsTest(_RemoveListeners, _fixtures.FixtureTest):
|
|
|
|
""""test event listeners against unmapped classes.
|
|
|
|
This incurs special logic. Note if we ever do the "remove" case,
|
|
it has to get all of these, too.
|
|
|
|
"""
|
|
run_inserts = None
|
|
|
|
def test_deferred_map_event(self):
|
|
"""
|
|
1. mapper event listen on class
|
|
2. map class
|
|
3. event fire should receive event
|
|
|
|
"""
|
|
users, User = (self.tables.users,
|
|
self.classes.User)
|
|
|
|
canary = []
|
|
|
|
def evt(x, y, z):
|
|
canary.append(x)
|
|
event.listen(User, "before_insert", evt, raw=True)
|
|
|
|
m = mapper(User, users)
|
|
m.dispatch.before_insert(5, 6, 7)
|
|
eq_(canary, [5])
|
|
|
|
def test_deferred_map_event_subclass_propagate(self):
|
|
"""
|
|
1. mapper event listen on class, w propagate
|
|
2. map only subclass of class
|
|
3. event fire should receive event
|
|
|
|
"""
|
|
users, User = (self.tables.users,
|
|
self.classes.User)
|
|
|
|
class SubUser(User):
|
|
pass
|
|
|
|
class SubSubUser(SubUser):
|
|
pass
|
|
|
|
canary = Mock()
|
|
|
|
def evt(x, y, z):
|
|
canary.append(x)
|
|
event.listen(User, "before_insert", canary, propagate=True, raw=True)
|
|
|
|
m = mapper(SubUser, users)
|
|
m.dispatch.before_insert(5, 6, 7)
|
|
eq_(canary.mock_calls,
|
|
[call(5, 6, 7)])
|
|
|
|
m2 = mapper(SubSubUser, users)
|
|
|
|
m2.dispatch.before_insert(8, 9, 10)
|
|
eq_(canary.mock_calls,
|
|
[call(5, 6, 7), call(8, 9, 10)])
|
|
|
|
def test_deferred_map_event_subclass_no_propagate(self):
|
|
"""
|
|
1. mapper event listen on class, w/o propagate
|
|
2. map only subclass of class
|
|
3. event fire should not receive event
|
|
|
|
"""
|
|
users, User = (self.tables.users,
|
|
self.classes.User)
|
|
|
|
class SubUser(User):
|
|
pass
|
|
|
|
canary = []
|
|
|
|
def evt(x, y, z):
|
|
canary.append(x)
|
|
event.listen(User, "before_insert", evt, propagate=False)
|
|
|
|
m = mapper(SubUser, users)
|
|
m.dispatch.before_insert(5, 6, 7)
|
|
eq_(canary, [])
|
|
|
|
def test_deferred_map_event_subclass_post_mapping_propagate(self):
|
|
"""
|
|
1. map only subclass of class
|
|
2. mapper event listen on class, w propagate
|
|
3. event fire should receive event
|
|
|
|
"""
|
|
users, User = (self.tables.users,
|
|
self.classes.User)
|
|
|
|
class SubUser(User):
|
|
pass
|
|
|
|
m = mapper(SubUser, users)
|
|
|
|
canary = []
|
|
|
|
def evt(x, y, z):
|
|
canary.append(x)
|
|
event.listen(User, "before_insert", evt, propagate=True, raw=True)
|
|
|
|
m.dispatch.before_insert(5, 6, 7)
|
|
eq_(canary, [5])
|
|
|
|
def test_deferred_map_event_subclass_post_mapping_propagate_two(self):
|
|
"""
|
|
1. map only subclass of class
|
|
2. mapper event listen on class, w propagate
|
|
3. event fire should receive event
|
|
|
|
"""
|
|
users, User = (self.tables.users,
|
|
self.classes.User)
|
|
|
|
class SubUser(User):
|
|
pass
|
|
|
|
class SubSubUser(SubUser):
|
|
pass
|
|
|
|
m = mapper(SubUser, users)
|
|
|
|
canary = Mock()
|
|
event.listen(User, "before_insert", canary, propagate=True, raw=True)
|
|
|
|
m2 = mapper(SubSubUser, users)
|
|
|
|
m.dispatch.before_insert(5, 6, 7)
|
|
eq_(canary.mock_calls, [call(5, 6, 7)])
|
|
|
|
m2.dispatch.before_insert(8, 9, 10)
|
|
eq_(canary.mock_calls, [call(5, 6, 7), call(8, 9, 10)])
|
|
|
|
def test_deferred_instance_event_subclass_post_mapping_propagate(self):
|
|
"""
|
|
1. map only subclass of class
|
|
2. instance event listen on class, w propagate
|
|
3. event fire should receive event
|
|
|
|
"""
|
|
users, User = (self.tables.users,
|
|
self.classes.User)
|
|
|
|
class SubUser(User):
|
|
pass
|
|
|
|
m = mapper(SubUser, users)
|
|
|
|
canary = []
|
|
|
|
def evt(x):
|
|
canary.append(x)
|
|
event.listen(User, "load", evt, propagate=True, raw=True)
|
|
|
|
m.class_manager.dispatch.load(5)
|
|
eq_(canary, [5])
|
|
|
|
def test_deferred_instance_event_plain(self):
|
|
"""
|
|
1. instance event listen on class, w/o propagate
|
|
2. map class
|
|
3. event fire should receive event
|
|
|
|
"""
|
|
users, User = (self.tables.users,
|
|
self.classes.User)
|
|
|
|
canary = []
|
|
|
|
def evt(x):
|
|
canary.append(x)
|
|
event.listen(User, "load", evt, raw=True)
|
|
|
|
m = mapper(User, users)
|
|
m.class_manager.dispatch.load(5)
|
|
eq_(canary, [5])
|
|
|
|
def test_deferred_instance_event_subclass_propagate_subclass_only(self):
|
|
"""
|
|
1. instance event listen on class, w propagate
|
|
2. map two subclasses of class
|
|
3. event fire on each class should receive one and only one event
|
|
|
|
"""
|
|
users, User = (self.tables.users,
|
|
self.classes.User)
|
|
|
|
class SubUser(User):
|
|
pass
|
|
|
|
class SubUser2(User):
|
|
pass
|
|
|
|
canary = []
|
|
|
|
def evt(x):
|
|
canary.append(x)
|
|
event.listen(User, "load", evt, propagate=True, raw=True)
|
|
|
|
m = mapper(SubUser, users)
|
|
m2 = mapper(SubUser2, users)
|
|
|
|
m.class_manager.dispatch.load(5)
|
|
eq_(canary, [5])
|
|
|
|
m2.class_manager.dispatch.load(5)
|
|
eq_(canary, [5, 5])
|
|
|
|
def test_deferred_instance_event_subclass_propagate_baseclass(self):
|
|
"""
|
|
1. instance event listen on class, w propagate
|
|
2. map one subclass of class, map base class, leave 2nd subclass
|
|
unmapped
|
|
3. event fire on sub should receive one and only one event
|
|
4. event fire on base should receive one and only one event
|
|
5. map 2nd subclass
|
|
6. event fire on 2nd subclass should receive one and only one event
|
|
"""
|
|
users, User = (self.tables.users,
|
|
self.classes.User)
|
|
|
|
class SubUser(User):
|
|
pass
|
|
|
|
class SubUser2(User):
|
|
pass
|
|
|
|
canary = Mock()
|
|
event.listen(User, "load", canary, propagate=True, raw=False)
|
|
|
|
# reversing these fixes....
|
|
m = mapper(SubUser, users)
|
|
m2 = mapper(User, users)
|
|
|
|
instance = Mock()
|
|
m.class_manager.dispatch.load(instance)
|
|
|
|
eq_(canary.mock_calls, [call(instance.obj())])
|
|
|
|
m2.class_manager.dispatch.load(instance)
|
|
eq_(canary.mock_calls, [call(instance.obj()), call(instance.obj())])
|
|
|
|
m3 = mapper(SubUser2, users)
|
|
m3.class_manager.dispatch.load(instance)
|
|
eq_(canary.mock_calls, [call(instance.obj()),
|
|
call(instance.obj()), call(instance.obj())])
|
|
|
|
def test_deferred_instance_event_subclass_no_propagate(self):
|
|
"""
|
|
1. instance event listen on class, w/o propagate
|
|
2. map subclass
|
|
3. event fire on subclass should not receive event
|
|
"""
|
|
users, User = (self.tables.users,
|
|
self.classes.User)
|
|
|
|
class SubUser(User):
|
|
pass
|
|
|
|
canary = []
|
|
|
|
def evt(x):
|
|
canary.append(x)
|
|
event.listen(User, "load", evt, propagate=False)
|
|
|
|
m = mapper(SubUser, users)
|
|
m.class_manager.dispatch.load(5)
|
|
eq_(canary, [])
|
|
|
|
def test_deferred_instrument_event(self):
|
|
User = self.classes.User
|
|
|
|
canary = []
|
|
|
|
def evt(x):
|
|
canary.append(x)
|
|
event.listen(User, "attribute_instrument", evt)
|
|
|
|
instrumentation._instrumentation_factory.\
|
|
dispatch.attribute_instrument(User)
|
|
eq_(canary, [User])
|
|
|
|
def test_isolation_instrument_event(self):
|
|
User = self.classes.User
|
|
|
|
class Bar(object):
|
|
pass
|
|
|
|
canary = []
|
|
|
|
def evt(x):
|
|
canary.append(x)
|
|
event.listen(Bar, "attribute_instrument", evt)
|
|
|
|
instrumentation._instrumentation_factory.dispatch.\
|
|
attribute_instrument(User)
|
|
eq_(canary, [])
|
|
|
|
@testing.requires.predictable_gc
|
|
def test_instrument_event_auto_remove(self):
|
|
class Bar(object):
|
|
pass
|
|
|
|
dispatch = instrumentation._instrumentation_factory.dispatch
|
|
assert not dispatch.attribute_instrument
|
|
|
|
event.listen(Bar, "attribute_instrument", lambda: None)
|
|
|
|
eq_(len(dispatch.attribute_instrument), 1)
|
|
|
|
del Bar
|
|
gc_collect()
|
|
|
|
assert not dispatch.attribute_instrument
|
|
|
|
def test_deferred_instrument_event_subclass_propagate(self):
|
|
User = self.classes.User
|
|
|
|
class SubUser(User):
|
|
pass
|
|
|
|
canary = []
|
|
|
|
def evt(x):
|
|
canary.append(x)
|
|
event.listen(User, "attribute_instrument", evt, propagate=True)
|
|
|
|
instrumentation._instrumentation_factory.dispatch.\
|
|
attribute_instrument(SubUser)
|
|
eq_(canary, [SubUser])
|
|
|
|
def test_deferred_instrument_event_subclass_no_propagate(self):
|
|
users, User = (self.tables.users,
|
|
self.classes.User)
|
|
|
|
class SubUser(User):
|
|
pass
|
|
|
|
canary = []
|
|
|
|
def evt(x):
|
|
canary.append(x)
|
|
event.listen(User, "attribute_instrument", evt, propagate=False)
|
|
|
|
mapper(SubUser, users)
|
|
instrumentation._instrumentation_factory.dispatch.\
|
|
attribute_instrument(5)
|
|
eq_(canary, [])
|
|
|
|
|
|
class LoadTest(_fixtures.FixtureTest):
|
|
run_inserts = None
|
|
|
|
@classmethod
|
|
def setup_mappers(cls):
|
|
User, users = cls.classes.User, cls.tables.users
|
|
|
|
mapper(User, users)
|
|
|
|
def _fixture(self):
|
|
User = self.classes.User
|
|
|
|
canary = []
|
|
|
|
def load(target, ctx):
|
|
canary.append("load")
|
|
|
|
def refresh(target, ctx, attrs):
|
|
canary.append(("refresh", attrs))
|
|
|
|
event.listen(User, "load", load)
|
|
event.listen(User, "refresh", refresh)
|
|
return canary
|
|
|
|
def test_just_loaded(self):
|
|
User = self.classes.User
|
|
|
|
canary = self._fixture()
|
|
|
|
sess = Session()
|
|
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
sess.commit()
|
|
sess.close()
|
|
|
|
sess.query(User).first()
|
|
eq_(canary, ['load'])
|
|
|
|
def test_repeated_rows(self):
|
|
User = self.classes.User
|
|
|
|
canary = self._fixture()
|
|
|
|
sess = Session()
|
|
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
sess.commit()
|
|
sess.close()
|
|
|
|
sess.query(User).union_all(sess.query(User)).all()
|
|
eq_(canary, ['load'])
|
|
|
|
|
|
class RemovalTest(_fixtures.FixtureTest):
|
|
run_inserts = None
|
|
|
|
def test_attr_propagated(self):
|
|
User = self.classes.User
|
|
|
|
users, addresses, User = (self.tables.users,
|
|
self.tables.addresses,
|
|
self.classes.User)
|
|
|
|
class AdminUser(User):
|
|
pass
|
|
|
|
mapper(User, users)
|
|
mapper(AdminUser, addresses, inherits=User,
|
|
properties={'address_id': addresses.c.id})
|
|
|
|
fn = Mock()
|
|
event.listen(User.name, "set", fn, propagate=True)
|
|
|
|
au = AdminUser()
|
|
au.name = 'ed'
|
|
|
|
eq_(fn.call_count, 1)
|
|
|
|
event.remove(User.name, "set", fn)
|
|
|
|
au.name = 'jack'
|
|
|
|
eq_(fn.call_count, 1)
|
|
|
|
def test_unmapped_listen(self):
|
|
users = self.tables.users
|
|
|
|
class Foo(object):
|
|
pass
|
|
|
|
fn = Mock()
|
|
|
|
event.listen(Foo, "before_insert", fn, propagate=True)
|
|
|
|
class User(Foo):
|
|
pass
|
|
|
|
m = mapper(User, users)
|
|
|
|
u1 = User()
|
|
m.dispatch.before_insert(m, None, attributes.instance_state(u1))
|
|
eq_(fn.call_count, 1)
|
|
|
|
event.remove(Foo, "before_insert", fn)
|
|
|
|
# existing event is removed
|
|
m.dispatch.before_insert(m, None, attributes.instance_state(u1))
|
|
eq_(fn.call_count, 1)
|
|
|
|
# the _HoldEvents is also cleaned out
|
|
class Bar(Foo):
|
|
pass
|
|
m = mapper(Bar, users)
|
|
b1 = Bar()
|
|
m.dispatch.before_insert(m, None, attributes.instance_state(b1))
|
|
eq_(fn.call_count, 1)
|
|
|
|
def test_instance_event_listen_on_cls_before_map(self):
|
|
users = self.tables.users
|
|
|
|
fn = Mock()
|
|
|
|
class User(object):
|
|
pass
|
|
|
|
event.listen(User, "load", fn)
|
|
m = mapper(User, users)
|
|
|
|
u1 = User()
|
|
m.class_manager.dispatch.load(u1._sa_instance_state, "u1")
|
|
|
|
event.remove(User, "load", fn)
|
|
|
|
m.class_manager.dispatch.load(u1._sa_instance_state, "u2")
|
|
|
|
eq_(fn.mock_calls, [call(u1, "u1")])
|
|
|
|
|
|
class RefreshTest(_fixtures.FixtureTest):
|
|
run_inserts = None
|
|
|
|
@classmethod
|
|
def setup_mappers(cls):
|
|
User, users = cls.classes.User, cls.tables.users
|
|
|
|
mapper(User, users)
|
|
|
|
def _fixture(self):
|
|
User = self.classes.User
|
|
|
|
canary = []
|
|
|
|
def load(target, ctx):
|
|
canary.append("load")
|
|
|
|
def refresh(target, ctx, attrs):
|
|
canary.append(("refresh", attrs))
|
|
|
|
event.listen(User, "load", load)
|
|
event.listen(User, "refresh", refresh)
|
|
return canary
|
|
|
|
def test_already_present(self):
|
|
User = self.classes.User
|
|
|
|
canary = self._fixture()
|
|
|
|
sess = Session()
|
|
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
sess.flush()
|
|
|
|
sess.query(User).first()
|
|
eq_(canary, [])
|
|
|
|
def test_changes_reset(self):
|
|
"""test the contract of load/refresh such that history is reset.
|
|
|
|
This has never been an official contract but we are testing it
|
|
here to ensure it is maintained given the loading performance
|
|
enhancements.
|
|
|
|
"""
|
|
User = self.classes.User
|
|
|
|
@event.listens_for(User, "load")
|
|
def canary1(obj, context):
|
|
obj.name = 'new name!'
|
|
|
|
@event.listens_for(User, "refresh")
|
|
def canary2(obj, context, props):
|
|
obj.name = 'refreshed name!'
|
|
|
|
sess = Session()
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
sess.commit()
|
|
sess.close()
|
|
|
|
u1 = sess.query(User).first()
|
|
eq_(
|
|
attributes.get_history(u1, "name"),
|
|
((), ['new name!'], ())
|
|
)
|
|
assert "name" not in attributes.instance_state(u1).committed_state
|
|
assert u1 not in sess.dirty
|
|
|
|
sess.expire(u1)
|
|
u1.id
|
|
eq_(
|
|
attributes.get_history(u1, "name"),
|
|
((), ['refreshed name!'], ())
|
|
)
|
|
assert "name" not in attributes.instance_state(u1).committed_state
|
|
assert u1 in sess.dirty
|
|
|
|
def test_repeated_rows(self):
|
|
User = self.classes.User
|
|
|
|
canary = self._fixture()
|
|
|
|
sess = Session()
|
|
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
sess.commit()
|
|
|
|
sess.query(User).union_all(sess.query(User)).all()
|
|
eq_(canary, [('refresh', set(['id', 'name']))])
|
|
|
|
def test_via_refresh_state(self):
|
|
User = self.classes.User
|
|
|
|
canary = self._fixture()
|
|
|
|
sess = Session()
|
|
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
sess.commit()
|
|
|
|
u1.name
|
|
eq_(canary, [('refresh', set(['id', 'name']))])
|
|
|
|
def test_was_expired(self):
|
|
User = self.classes.User
|
|
|
|
canary = self._fixture()
|
|
|
|
sess = Session()
|
|
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
sess.flush()
|
|
sess.expire(u1)
|
|
|
|
sess.query(User).first()
|
|
eq_(canary, [('refresh', set(['id', 'name']))])
|
|
|
|
def test_was_expired_via_commit(self):
|
|
User = self.classes.User
|
|
|
|
canary = self._fixture()
|
|
|
|
sess = Session()
|
|
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
sess.commit()
|
|
|
|
sess.query(User).first()
|
|
eq_(canary, [('refresh', set(['id', 'name']))])
|
|
|
|
def test_was_expired_attrs(self):
|
|
User = self.classes.User
|
|
|
|
canary = self._fixture()
|
|
|
|
sess = Session()
|
|
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
sess.flush()
|
|
sess.expire(u1, ['name'])
|
|
|
|
sess.query(User).first()
|
|
eq_(canary, [('refresh', set(['name']))])
|
|
|
|
def test_populate_existing(self):
|
|
User = self.classes.User
|
|
|
|
canary = self._fixture()
|
|
|
|
sess = Session()
|
|
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
sess.commit()
|
|
|
|
sess.query(User).populate_existing().first()
|
|
eq_(canary, [('refresh', None)])
|
|
|
|
|
|
class SessionEventsTest(_RemoveListeners, _fixtures.FixtureTest):
|
|
run_inserts = None
|
|
|
|
def test_class_listen(self):
|
|
def my_listener(*arg, **kw):
|
|
pass
|
|
|
|
event.listen(Session, 'before_flush', my_listener)
|
|
|
|
s = Session()
|
|
assert my_listener in s.dispatch.before_flush
|
|
|
|
def test_sessionmaker_listen(self):
|
|
"""test that listen can be applied to individual
|
|
scoped_session() classes."""
|
|
|
|
def my_listener_one(*arg, **kw):
|
|
pass
|
|
|
|
def my_listener_two(*arg, **kw):
|
|
pass
|
|
|
|
S1 = sessionmaker()
|
|
S2 = sessionmaker()
|
|
|
|
event.listen(Session, 'before_flush', my_listener_one)
|
|
event.listen(S1, 'before_flush', my_listener_two)
|
|
|
|
s1 = S1()
|
|
assert my_listener_one in s1.dispatch.before_flush
|
|
assert my_listener_two in s1.dispatch.before_flush
|
|
|
|
s2 = S2()
|
|
assert my_listener_one in s2.dispatch.before_flush
|
|
assert my_listener_two not in s2.dispatch.before_flush
|
|
|
|
def test_scoped_session_invalid_callable(self):
|
|
from sqlalchemy.orm import scoped_session
|
|
|
|
def my_listener_one(*arg, **kw):
|
|
pass
|
|
|
|
scope = scoped_session(lambda: Session())
|
|
|
|
assert_raises_message(
|
|
sa.exc.ArgumentError,
|
|
"Session event listen on a scoped_session requires that its "
|
|
"creation callable is associated with the Session class.",
|
|
event.listen, scope, "before_flush", my_listener_one
|
|
)
|
|
|
|
def test_scoped_session_invalid_class(self):
|
|
from sqlalchemy.orm import scoped_session
|
|
|
|
def my_listener_one(*arg, **kw):
|
|
pass
|
|
|
|
class NotASession(object):
|
|
|
|
def __call__(self):
|
|
return Session()
|
|
|
|
scope = scoped_session(NotASession)
|
|
|
|
assert_raises_message(
|
|
sa.exc.ArgumentError,
|
|
"Session event listen on a scoped_session requires that its "
|
|
"creation callable is associated with the Session class.",
|
|
event.listen, scope, "before_flush", my_listener_one
|
|
)
|
|
|
|
def test_scoped_session_listen(self):
|
|
from sqlalchemy.orm import scoped_session
|
|
|
|
def my_listener_one(*arg, **kw):
|
|
pass
|
|
|
|
scope = scoped_session(sessionmaker())
|
|
event.listen(scope, "before_flush", my_listener_one)
|
|
|
|
assert my_listener_one in scope().dispatch.before_flush
|
|
|
|
def _listener_fixture(self, **kw):
|
|
canary = []
|
|
|
|
def listener(name):
|
|
def go(*arg, **kw):
|
|
canary.append(name)
|
|
return go
|
|
|
|
sess = Session(**kw)
|
|
|
|
for evt in [
|
|
'after_transaction_create',
|
|
'after_transaction_end',
|
|
'before_commit',
|
|
'after_commit',
|
|
'after_rollback',
|
|
'after_soft_rollback',
|
|
'before_flush',
|
|
'after_flush',
|
|
'after_flush_postexec',
|
|
'after_begin',
|
|
'before_attach',
|
|
'after_attach',
|
|
'after_bulk_update',
|
|
'after_bulk_delete'
|
|
]:
|
|
event.listen(sess, evt, listener(evt))
|
|
|
|
return sess, canary
|
|
|
|
def test_flush_autocommit_hook(self):
|
|
User, users = self.classes.User, self.tables.users
|
|
|
|
mapper(User, users)
|
|
|
|
sess, canary = self._listener_fixture(
|
|
autoflush=False,
|
|
autocommit=True, expire_on_commit=False)
|
|
|
|
u = User(name='u1')
|
|
sess.add(u)
|
|
sess.flush()
|
|
eq_(
|
|
canary,
|
|
['before_attach', 'after_attach', 'before_flush',
|
|
'after_transaction_create', 'after_begin',
|
|
'after_flush', 'after_flush_postexec',
|
|
'before_commit', 'after_commit', 'after_transaction_end']
|
|
)
|
|
|
|
def test_rollback_hook(self):
|
|
User, users = self.classes.User, self.tables.users
|
|
sess, canary = self._listener_fixture()
|
|
mapper(User, users)
|
|
|
|
u = User(name='u1', id=1)
|
|
sess.add(u)
|
|
sess.commit()
|
|
|
|
u2 = User(name='u1', id=1)
|
|
sess.add(u2)
|
|
assert_raises(
|
|
sa.orm.exc.FlushError,
|
|
sess.commit
|
|
)
|
|
sess.rollback()
|
|
eq_(canary,
|
|
|
|
['before_attach', 'after_attach', 'before_commit', 'before_flush',
|
|
'after_transaction_create', 'after_begin', 'after_flush',
|
|
'after_flush_postexec', 'after_transaction_end', 'after_commit',
|
|
'after_transaction_end', 'after_transaction_create',
|
|
'before_attach', 'after_attach', 'before_commit',
|
|
'before_flush', 'after_transaction_create', 'after_begin',
|
|
'after_rollback',
|
|
'after_transaction_end',
|
|
'after_soft_rollback', 'after_transaction_end',
|
|
'after_transaction_create',
|
|
'after_soft_rollback'])
|
|
|
|
def test_can_use_session_in_outer_rollback_hook(self):
|
|
User, users = self.classes.User, self.tables.users
|
|
mapper(User, users)
|
|
|
|
sess = Session()
|
|
|
|
assertions = []
|
|
|
|
@event.listens_for(sess, "after_soft_rollback")
|
|
def do_something(session, previous_transaction):
|
|
if session.is_active:
|
|
assertions.append('name' not in u.__dict__)
|
|
assertions.append(u.name == 'u1')
|
|
|
|
u = User(name='u1', id=1)
|
|
sess.add(u)
|
|
sess.commit()
|
|
|
|
u2 = User(name='u1', id=1)
|
|
sess.add(u2)
|
|
assert_raises(
|
|
sa.orm.exc.FlushError,
|
|
sess.commit
|
|
)
|
|
sess.rollback()
|
|
eq_(assertions, [True, True])
|
|
|
|
def test_flush_noautocommit_hook(self):
|
|
User, users = self.classes.User, self.tables.users
|
|
|
|
sess, canary = self._listener_fixture()
|
|
|
|
mapper(User, users)
|
|
|
|
u = User(name='u1')
|
|
sess.add(u)
|
|
sess.flush()
|
|
eq_(canary, ['before_attach', 'after_attach', 'before_flush',
|
|
'after_transaction_create', 'after_begin',
|
|
'after_flush', 'after_flush_postexec',
|
|
'after_transaction_end'])
|
|
|
|
def test_flush_in_commit_hook(self):
|
|
User, users = self.classes.User, self.tables.users
|
|
|
|
sess, canary = self._listener_fixture()
|
|
|
|
mapper(User, users)
|
|
u = User(name='u1')
|
|
sess.add(u)
|
|
sess.flush()
|
|
canary[:] = []
|
|
|
|
u.name = 'ed'
|
|
sess.commit()
|
|
eq_(canary, ['before_commit', 'before_flush',
|
|
'after_transaction_create', 'after_flush',
|
|
'after_flush_postexec',
|
|
'after_transaction_end',
|
|
'after_commit',
|
|
'after_transaction_end', 'after_transaction_create', ])
|
|
|
|
def test_state_before_attach(self):
|
|
User, users = self.classes.User, self.tables.users
|
|
sess = Session()
|
|
|
|
@event.listens_for(sess, "before_attach")
|
|
def listener(session, inst):
|
|
state = attributes.instance_state(inst)
|
|
if state.key:
|
|
assert state.key not in session.identity_map
|
|
else:
|
|
assert inst not in session.new
|
|
|
|
mapper(User, users)
|
|
u = User(name='u1')
|
|
sess.add(u)
|
|
sess.flush()
|
|
sess.expunge(u)
|
|
sess.add(u)
|
|
|
|
def test_state_after_attach(self):
|
|
User, users = self.classes.User, self.tables.users
|
|
sess = Session()
|
|
|
|
@event.listens_for(sess, "after_attach")
|
|
def listener(session, inst):
|
|
state = attributes.instance_state(inst)
|
|
if state.key:
|
|
assert session.identity_map[state.key] is inst
|
|
else:
|
|
assert inst in session.new
|
|
|
|
mapper(User, users)
|
|
u = User(name='u1')
|
|
sess.add(u)
|
|
sess.flush()
|
|
sess.expunge(u)
|
|
sess.add(u)
|
|
|
|
def test_standalone_on_commit_hook(self):
|
|
sess, canary = self._listener_fixture()
|
|
sess.commit()
|
|
eq_(canary, ['before_commit', 'after_commit',
|
|
'after_transaction_end',
|
|
'after_transaction_create'])
|
|
|
|
def test_on_bulk_update_hook(self):
|
|
User, users = self.classes.User, self.tables.users
|
|
|
|
sess = Session()
|
|
canary = Mock()
|
|
|
|
event.listen(sess, "after_begin", canary.after_begin)
|
|
event.listen(sess, "after_bulk_update", canary.after_bulk_update)
|
|
|
|
def legacy(ses, qry, ctx, res):
|
|
canary.after_bulk_update_legacy(ses, qry, ctx, res)
|
|
event.listen(sess, "after_bulk_update", legacy)
|
|
|
|
mapper(User, users)
|
|
|
|
sess.query(User).update({'name': 'foo'})
|
|
|
|
eq_(
|
|
canary.after_begin.call_count,
|
|
1
|
|
)
|
|
eq_(
|
|
canary.after_bulk_update.call_count,
|
|
1
|
|
)
|
|
|
|
upd = canary.after_bulk_update.mock_calls[0][1][0]
|
|
eq_(
|
|
upd.session,
|
|
sess
|
|
)
|
|
eq_(
|
|
canary.after_bulk_update_legacy.mock_calls,
|
|
[call(sess, upd.query, upd.context, upd.result)]
|
|
)
|
|
|
|
def test_on_bulk_delete_hook(self):
|
|
User, users = self.classes.User, self.tables.users
|
|
|
|
sess = Session()
|
|
canary = Mock()
|
|
|
|
event.listen(sess, "after_begin", canary.after_begin)
|
|
event.listen(sess, "after_bulk_delete", canary.after_bulk_delete)
|
|
|
|
def legacy(ses, qry, ctx, res):
|
|
canary.after_bulk_delete_legacy(ses, qry, ctx, res)
|
|
event.listen(sess, "after_bulk_delete", legacy)
|
|
|
|
mapper(User, users)
|
|
|
|
sess.query(User).delete()
|
|
|
|
eq_(
|
|
canary.after_begin.call_count,
|
|
1
|
|
)
|
|
eq_(
|
|
canary.after_bulk_delete.call_count,
|
|
1
|
|
)
|
|
|
|
upd = canary.after_bulk_delete.mock_calls[0][1][0]
|
|
eq_(
|
|
upd.session,
|
|
sess
|
|
)
|
|
eq_(
|
|
canary.after_bulk_delete_legacy.mock_calls,
|
|
[call(sess, upd.query, upd.context, upd.result)]
|
|
)
|
|
|
|
def test_connection_emits_after_begin(self):
|
|
sess, canary = self._listener_fixture(bind=testing.db)
|
|
sess.connection()
|
|
eq_(canary, ['after_begin'])
|
|
sess.close()
|
|
|
|
def test_reentrant_flush(self):
|
|
users, User = self.tables.users, self.classes.User
|
|
|
|
mapper(User, users)
|
|
|
|
def before_flush(session, flush_context, objects):
|
|
session.flush()
|
|
|
|
sess = Session()
|
|
event.listen(sess, 'before_flush', before_flush)
|
|
sess.add(User(name='foo'))
|
|
assert_raises_message(sa.exc.InvalidRequestError,
|
|
'already flushing', sess.flush)
|
|
|
|
def test_before_flush_affects_flush_plan(self):
|
|
users, User = self.tables.users, self.classes.User
|
|
|
|
mapper(User, users)
|
|
|
|
def before_flush(session, flush_context, objects):
|
|
for obj in list(session.new) + list(session.dirty):
|
|
if isinstance(obj, User):
|
|
session.add(User(name='another %s' % obj.name))
|
|
for obj in list(session.deleted):
|
|
if isinstance(obj, User):
|
|
x = session.query(User).filter(
|
|
User.name == 'another %s' % obj.name).one()
|
|
session.delete(x)
|
|
|
|
sess = Session()
|
|
event.listen(sess, 'before_flush', before_flush)
|
|
|
|
u = User(name='u1')
|
|
sess.add(u)
|
|
sess.flush()
|
|
eq_(sess.query(User).order_by(User.name).all(),
|
|
[
|
|
User(name='another u1'),
|
|
User(name='u1')
|
|
]
|
|
)
|
|
|
|
sess.flush()
|
|
eq_(sess.query(User).order_by(User.name).all(),
|
|
[
|
|
User(name='another u1'),
|
|
User(name='u1')
|
|
]
|
|
)
|
|
|
|
u.name = 'u2'
|
|
sess.flush()
|
|
eq_(sess.query(User).order_by(User.name).all(),
|
|
[
|
|
User(name='another u1'),
|
|
User(name='another u2'),
|
|
User(name='u2')
|
|
]
|
|
)
|
|
|
|
sess.delete(u)
|
|
sess.flush()
|
|
eq_(sess.query(User).order_by(User.name).all(),
|
|
[
|
|
User(name='another u1'),
|
|
]
|
|
)
|
|
|
|
def test_before_flush_affects_dirty(self):
|
|
users, User = self.tables.users, self.classes.User
|
|
|
|
mapper(User, users)
|
|
|
|
def before_flush(session, flush_context, objects):
|
|
for obj in list(session.identity_map.values()):
|
|
obj.name += " modified"
|
|
|
|
sess = Session(autoflush=True)
|
|
event.listen(sess, 'before_flush', before_flush)
|
|
|
|
u = User(name='u1')
|
|
sess.add(u)
|
|
sess.flush()
|
|
eq_(sess.query(User).order_by(User.name).all(),
|
|
[User(name='u1')]
|
|
)
|
|
|
|
sess.add(User(name='u2'))
|
|
sess.flush()
|
|
sess.expunge_all()
|
|
eq_(sess.query(User).order_by(User.name).all(),
|
|
[
|
|
User(name='u1 modified'),
|
|
User(name='u2')
|
|
]
|
|
)
|
|
|
|
def test_snapshot_still_present_after_commit(self):
|
|
users, User = self.tables.users, self.classes.User
|
|
|
|
mapper(User, users)
|
|
|
|
sess = Session()
|
|
|
|
u1 = User(name='u1')
|
|
|
|
sess.add(u1)
|
|
sess.commit()
|
|
|
|
u1 = sess.query(User).first()
|
|
|
|
@event.listens_for(sess, "after_commit")
|
|
def assert_state(session):
|
|
assert 'name' in u1.__dict__
|
|
eq_(u1.name, 'u1')
|
|
|
|
sess.commit()
|
|
assert 'name' not in u1.__dict__
|
|
|
|
def test_snapshot_still_present_after_rollback(self):
|
|
users, User = self.tables.users, self.classes.User
|
|
|
|
mapper(User, users)
|
|
|
|
sess = Session()
|
|
|
|
u1 = User(name='u1')
|
|
|
|
sess.add(u1)
|
|
sess.commit()
|
|
|
|
u1 = sess.query(User).first()
|
|
|
|
@event.listens_for(sess, "after_rollback")
|
|
def assert_state(session):
|
|
assert 'name' in u1.__dict__
|
|
eq_(u1.name, 'u1')
|
|
|
|
sess.rollback()
|
|
assert 'name' not in u1.__dict__
|
|
|
|
|
|
class SessionLifecycleEventsTest(_RemoveListeners, _fixtures.FixtureTest):
|
|
run_inserts = None
|
|
|
|
def _fixture(self, include_address=False):
|
|
users, User = self.tables.users, self.classes.User
|
|
|
|
if include_address:
|
|
addresses, Address = self.tables.addresses, self.classes.Address
|
|
mapper(User, users, properties={
|
|
"addresses": relationship(
|
|
Address, cascade="all, delete-orphan")
|
|
})
|
|
mapper(Address, addresses)
|
|
else:
|
|
mapper(User, users)
|
|
|
|
listener = Mock()
|
|
|
|
sess = Session()
|
|
|
|
def start_events():
|
|
event.listen(
|
|
sess, "transient_to_pending", listener.transient_to_pending)
|
|
event.listen(
|
|
sess, "pending_to_transient", listener.pending_to_transient)
|
|
event.listen(
|
|
sess, "persistent_to_transient",
|
|
listener.persistent_to_transient)
|
|
event.listen(
|
|
sess, "pending_to_persistent", listener.pending_to_persistent)
|
|
event.listen(
|
|
sess, "detached_to_persistent",
|
|
listener.detached_to_persistent)
|
|
event.listen(
|
|
sess, "loaded_as_persistent", listener.loaded_as_persistent)
|
|
|
|
event.listen(
|
|
sess, "persistent_to_detached",
|
|
listener.persistent_to_detached)
|
|
event.listen(
|
|
sess, "deleted_to_detached", listener.deleted_to_detached)
|
|
|
|
event.listen(
|
|
sess, "persistent_to_deleted", listener.persistent_to_deleted)
|
|
event.listen(
|
|
sess, "deleted_to_persistent", listener.deleted_to_persistent)
|
|
return listener
|
|
|
|
if include_address:
|
|
return sess, User, Address, start_events
|
|
else:
|
|
return sess, User, start_events
|
|
|
|
def test_transient_to_pending(self):
|
|
sess, User, start_events = self._fixture()
|
|
|
|
listener = start_events()
|
|
|
|
@event.listens_for(sess, "transient_to_pending")
|
|
def trans_to_pending(session, instance):
|
|
assert instance in session
|
|
listener.flag_checked(instance)
|
|
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
|
|
eq_(
|
|
listener.mock_calls,
|
|
[
|
|
call.transient_to_pending(sess, u1),
|
|
call.flag_checked(u1)
|
|
]
|
|
)
|
|
|
|
def test_pending_to_transient_via_rollback(self):
|
|
sess, User, start_events = self._fixture()
|
|
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
|
|
listener = start_events()
|
|
|
|
@event.listens_for(sess, "pending_to_transient")
|
|
def test_deleted_flag(session, instance):
|
|
assert instance not in session
|
|
listener.flag_checked(instance)
|
|
|
|
sess.rollback()
|
|
assert u1 not in sess
|
|
|
|
eq_(
|
|
listener.mock_calls,
|
|
[
|
|
call.pending_to_transient(sess, u1),
|
|
call.flag_checked(u1)
|
|
]
|
|
)
|
|
|
|
def test_pending_to_transient_via_expunge(self):
|
|
sess, User, start_events = self._fixture()
|
|
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
|
|
listener = start_events()
|
|
|
|
@event.listens_for(sess, "pending_to_transient")
|
|
def test_deleted_flag(session, instance):
|
|
assert instance not in session
|
|
listener.flag_checked(instance)
|
|
|
|
sess.expunge(u1)
|
|
assert u1 not in sess
|
|
|
|
eq_(
|
|
listener.mock_calls,
|
|
[
|
|
call.pending_to_transient(sess, u1),
|
|
call.flag_checked(u1)
|
|
]
|
|
)
|
|
|
|
def test_pending_to_persistent(self):
|
|
sess, User, start_events = self._fixture()
|
|
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
|
|
listener = start_events()
|
|
|
|
@event.listens_for(sess, "pending_to_persistent")
|
|
def test_flag(session, instance):
|
|
assert instance in session
|
|
assert instance._sa_instance_state.persistent
|
|
assert instance._sa_instance_state.key in session.identity_map
|
|
listener.flag_checked(instance)
|
|
|
|
sess.flush()
|
|
|
|
eq_(
|
|
listener.mock_calls,
|
|
[
|
|
call.pending_to_persistent(sess, u1),
|
|
call.flag_checked(u1)
|
|
]
|
|
)
|
|
|
|
def test_pending_to_persistent_del(self):
|
|
sess, User, start_events = self._fixture()
|
|
|
|
@event.listens_for(sess, "pending_to_persistent")
|
|
def pending_to_persistent(session, instance):
|
|
listener.flag_checked(instance)
|
|
# this is actually u1, because
|
|
# we have a strong ref internally
|
|
is_not_(None, instance)
|
|
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
|
|
u1_inst_state = u1._sa_instance_state
|
|
del u1
|
|
|
|
gc_collect()
|
|
|
|
listener = start_events()
|
|
|
|
sess.flush()
|
|
|
|
eq_(
|
|
listener.mock_calls,
|
|
[
|
|
call.flag_checked(u1_inst_state.obj()),
|
|
call.pending_to_persistent(
|
|
sess, u1_inst_state.obj()),
|
|
]
|
|
)
|
|
|
|
def test_persistent_to_deleted_del(self):
|
|
sess, User, start_events = self._fixture()
|
|
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
sess.flush()
|
|
|
|
listener = start_events()
|
|
|
|
@event.listens_for(sess, "persistent_to_deleted")
|
|
def persistent_to_deleted(session, instance):
|
|
is_not_(None, instance)
|
|
listener.flag_checked(instance)
|
|
|
|
sess.delete(u1)
|
|
u1_inst_state = u1._sa_instance_state
|
|
|
|
del u1
|
|
gc_collect()
|
|
|
|
sess.flush()
|
|
|
|
eq_(
|
|
listener.mock_calls,
|
|
[
|
|
call.persistent_to_deleted(sess, u1_inst_state.obj()),
|
|
call.flag_checked(u1_inst_state.obj())
|
|
]
|
|
)
|
|
|
|
def test_detached_to_persistent(self):
|
|
sess, User, start_events = self._fixture()
|
|
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
sess.flush()
|
|
|
|
sess.expunge(u1)
|
|
|
|
listener = start_events()
|
|
|
|
@event.listens_for(sess, "detached_to_persistent")
|
|
def test_deleted_flag(session, instance):
|
|
assert instance not in session.deleted
|
|
assert instance in session
|
|
listener.flag_checked()
|
|
|
|
sess.add(u1)
|
|
|
|
eq_(
|
|
listener.mock_calls,
|
|
[
|
|
call.detached_to_persistent(sess, u1),
|
|
call.flag_checked()
|
|
]
|
|
)
|
|
|
|
def test_loaded_as_persistent(self):
|
|
sess, User, start_events = self._fixture()
|
|
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
sess.commit()
|
|
sess.close()
|
|
|
|
listener = start_events()
|
|
|
|
@event.listens_for(sess, "loaded_as_persistent")
|
|
def test_identity_flag(session, instance):
|
|
assert instance in session
|
|
assert instance._sa_instance_state.persistent
|
|
assert instance._sa_instance_state.key in session.identity_map
|
|
assert not instance._sa_instance_state.deleted
|
|
assert not instance._sa_instance_state.detached
|
|
assert instance._sa_instance_state.persistent
|
|
listener.flag_checked(instance)
|
|
|
|
u1 = sess.query(User).filter_by(name='u1').one()
|
|
|
|
eq_(
|
|
listener.mock_calls,
|
|
[
|
|
call.loaded_as_persistent(sess, u1),
|
|
call.flag_checked(u1)
|
|
]
|
|
)
|
|
|
|
def test_detached_to_persistent_via_deleted(self):
|
|
sess, User, start_events = self._fixture()
|
|
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
sess.commit()
|
|
sess.close()
|
|
|
|
listener = start_events()
|
|
|
|
@event.listens_for(sess, "detached_to_persistent")
|
|
def test_deleted_flag_persistent(session, instance):
|
|
assert instance not in session.deleted
|
|
assert instance in session
|
|
assert not instance._sa_instance_state.deleted
|
|
assert not instance._sa_instance_state.detached
|
|
assert instance._sa_instance_state.persistent
|
|
listener.dtp_flag_checked(instance)
|
|
|
|
@event.listens_for(sess, "persistent_to_deleted")
|
|
def test_deleted_flag_detached(session, instance):
|
|
assert instance not in session.deleted
|
|
assert instance not in session
|
|
assert not instance._sa_instance_state.persistent
|
|
assert instance._sa_instance_state.deleted
|
|
assert not instance._sa_instance_state.detached
|
|
listener.ptd_flag_checked(instance)
|
|
|
|
sess.delete(u1)
|
|
assert u1 in sess.deleted
|
|
|
|
eq_(
|
|
listener.mock_calls,
|
|
[
|
|
call.detached_to_persistent(sess, u1),
|
|
call.dtp_flag_checked(u1)
|
|
]
|
|
)
|
|
|
|
sess.flush()
|
|
|
|
eq_(
|
|
listener.mock_calls,
|
|
[
|
|
call.detached_to_persistent(sess, u1),
|
|
call.dtp_flag_checked(u1),
|
|
call.persistent_to_deleted(sess, u1),
|
|
call.ptd_flag_checked(u1),
|
|
]
|
|
)
|
|
|
|
def test_detached_to_persistent_via_cascaded_delete(self):
|
|
sess, User, Address, start_events = self._fixture(include_address=True)
|
|
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
a1 = Address(email_address='e1')
|
|
u1.addresses.append(a1)
|
|
sess.commit()
|
|
u1.addresses # ensure u1.addresses refers to a1 before detachment
|
|
sess.close()
|
|
|
|
listener = start_events()
|
|
|
|
@event.listens_for(sess, "detached_to_persistent")
|
|
def test_deleted_flag(session, instance):
|
|
assert instance not in session.deleted
|
|
assert instance in session
|
|
assert not instance._sa_instance_state.deleted
|
|
assert not instance._sa_instance_state.detached
|
|
assert instance._sa_instance_state.persistent
|
|
listener.flag_checked(instance)
|
|
|
|
sess.delete(u1)
|
|
assert u1 in sess.deleted
|
|
assert a1 in sess.deleted
|
|
|
|
eq_(
|
|
listener.mock_calls,
|
|
[
|
|
call.detached_to_persistent(sess, u1),
|
|
call.flag_checked(u1),
|
|
call.detached_to_persistent(sess, a1),
|
|
call.flag_checked(a1),
|
|
]
|
|
)
|
|
|
|
sess.flush()
|
|
|
|
def test_persistent_to_deleted(self):
|
|
sess, User, start_events = self._fixture()
|
|
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
sess.commit()
|
|
|
|
listener = start_events()
|
|
|
|
@event.listens_for(sess, "persistent_to_deleted")
|
|
def test_deleted_flag(session, instance):
|
|
assert instance not in session.deleted
|
|
assert instance not in session
|
|
assert instance._sa_instance_state.deleted
|
|
assert not instance._sa_instance_state.detached
|
|
assert not instance._sa_instance_state.persistent
|
|
listener.flag_checked(instance)
|
|
|
|
sess.delete(u1)
|
|
assert u1 in sess.deleted
|
|
|
|
eq_(
|
|
listener.mock_calls,
|
|
[]
|
|
)
|
|
|
|
sess.flush()
|
|
assert u1 not in sess
|
|
|
|
eq_(
|
|
listener.mock_calls,
|
|
[
|
|
call.persistent_to_deleted(sess, u1),
|
|
call.flag_checked(u1)
|
|
]
|
|
)
|
|
|
|
def test_persistent_to_detached_via_expunge(self):
|
|
sess, User, start_events = self._fixture()
|
|
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
sess.flush()
|
|
|
|
listener = start_events()
|
|
|
|
@event.listens_for(sess, "persistent_to_detached")
|
|
def test_deleted_flag(session, instance):
|
|
assert instance not in session.deleted
|
|
assert instance not in session
|
|
assert not instance._sa_instance_state.deleted
|
|
assert instance._sa_instance_state.detached
|
|
assert not instance._sa_instance_state.persistent
|
|
listener.flag_checked(instance)
|
|
|
|
assert u1 in sess
|
|
sess.expunge(u1)
|
|
assert u1 not in sess
|
|
|
|
eq_(
|
|
listener.mock_calls,
|
|
[
|
|
call.persistent_to_detached(sess, u1),
|
|
call.flag_checked(u1)
|
|
]
|
|
)
|
|
|
|
def test_persistent_to_detached_via_expunge_all(self):
|
|
sess, User, start_events = self._fixture()
|
|
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
sess.flush()
|
|
|
|
listener = start_events()
|
|
|
|
@event.listens_for(sess, "persistent_to_detached")
|
|
def test_deleted_flag(session, instance):
|
|
assert instance not in session.deleted
|
|
assert instance not in session
|
|
assert not instance._sa_instance_state.deleted
|
|
assert instance._sa_instance_state.detached
|
|
assert not instance._sa_instance_state.persistent
|
|
listener.flag_checked(instance)
|
|
|
|
assert u1 in sess
|
|
sess.expunge_all()
|
|
assert u1 not in sess
|
|
|
|
eq_(
|
|
listener.mock_calls,
|
|
[
|
|
call.persistent_to_detached(sess, u1),
|
|
call.flag_checked(u1)
|
|
]
|
|
)
|
|
|
|
def test_persistent_to_transient_via_rollback(self):
|
|
sess, User, start_events = self._fixture()
|
|
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
sess.flush()
|
|
|
|
listener = start_events()
|
|
|
|
@event.listens_for(sess, "persistent_to_transient")
|
|
def test_deleted_flag(session, instance):
|
|
assert instance not in session.deleted
|
|
assert instance not in session
|
|
assert not instance._sa_instance_state.deleted
|
|
assert not instance._sa_instance_state.detached
|
|
assert not instance._sa_instance_state.persistent
|
|
assert instance._sa_instance_state.transient
|
|
listener.flag_checked(instance)
|
|
|
|
sess.rollback()
|
|
|
|
eq_(
|
|
listener.mock_calls,
|
|
[
|
|
call.persistent_to_transient(sess, u1),
|
|
call.flag_checked(u1)
|
|
]
|
|
)
|
|
|
|
def test_deleted_to_persistent_via_rollback(self):
|
|
sess, User, start_events = self._fixture()
|
|
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
sess.commit()
|
|
|
|
sess.delete(u1)
|
|
sess.flush()
|
|
|
|
listener = start_events()
|
|
|
|
@event.listens_for(sess, "deleted_to_persistent")
|
|
def test_deleted_flag(session, instance):
|
|
assert instance not in session.deleted
|
|
assert instance in session
|
|
assert not instance._sa_instance_state.deleted
|
|
assert not instance._sa_instance_state.detached
|
|
assert instance._sa_instance_state.persistent
|
|
listener.flag_checked(instance)
|
|
|
|
assert u1 not in sess
|
|
assert u1._sa_instance_state.deleted
|
|
assert not u1._sa_instance_state.persistent
|
|
assert not u1._sa_instance_state.detached
|
|
|
|
sess.rollback()
|
|
|
|
assert u1 in sess
|
|
assert u1._sa_instance_state.persistent
|
|
assert not u1._sa_instance_state.deleted
|
|
assert not u1._sa_instance_state.detached
|
|
|
|
eq_(
|
|
listener.mock_calls,
|
|
[
|
|
call.deleted_to_persistent(sess, u1),
|
|
call.flag_checked(u1)
|
|
]
|
|
)
|
|
|
|
def test_deleted_to_detached_via_commit(self):
|
|
sess, User, start_events = self._fixture()
|
|
|
|
u1 = User(name='u1')
|
|
sess.add(u1)
|
|
sess.commit()
|
|
|
|
sess.delete(u1)
|
|
sess.flush()
|
|
|
|
listener = start_events()
|
|
|
|
@event.listens_for(sess, "deleted_to_detached")
|
|
def test_detached_flag(session, instance):
|
|
assert instance not in session.deleted
|
|
assert instance not in session
|
|
assert not instance._sa_instance_state.deleted
|
|
assert instance._sa_instance_state.detached
|
|
listener.flag_checked(instance)
|
|
|
|
assert u1 not in sess
|
|
assert u1._sa_instance_state.deleted
|
|
assert not u1._sa_instance_state.persistent
|
|
assert not u1._sa_instance_state.detached
|
|
|
|
sess.commit()
|
|
|
|
assert u1 not in sess
|
|
assert not u1._sa_instance_state.deleted
|
|
assert u1._sa_instance_state.detached
|
|
|
|
eq_(
|
|
listener.mock_calls,
|
|
[
|
|
call.deleted_to_detached(sess, u1),
|
|
call.flag_checked(u1)
|
|
]
|
|
)
|
|
|
|
|
|
class MapperExtensionTest(_fixtures.FixtureTest):
|
|
|
|
"""Superseded by MapperEventsTest - test backwards
|
|
compatibility of MapperExtension."""
|
|
|
|
run_inserts = None
|
|
|
|
def extension(self):
|
|
methods = []
|
|
|
|
class Ext(sa.orm.MapperExtension):
|
|
|
|
def instrument_class(self, mapper, cls):
|
|
methods.append('instrument_class')
|
|
return sa.orm.EXT_CONTINUE
|
|
|
|
def init_instance(
|
|
self, mapper, class_, oldinit, instance, args, kwargs):
|
|
methods.append('init_instance')
|
|
return sa.orm.EXT_CONTINUE
|
|
|
|
def init_failed(
|
|
self, mapper, class_, oldinit, instance, args, kwargs):
|
|
methods.append('init_failed')
|
|
return sa.orm.EXT_CONTINUE
|
|
|
|
def reconstruct_instance(self, mapper, instance):
|
|
methods.append('reconstruct_instance')
|
|
return sa.orm.EXT_CONTINUE
|
|
|
|
def before_insert(self, mapper, connection, instance):
|
|
methods.append('before_insert')
|
|
return sa.orm.EXT_CONTINUE
|
|
|
|
def after_insert(self, mapper, connection, instance):
|
|
methods.append('after_insert')
|
|
return sa.orm.EXT_CONTINUE
|
|
|
|
def before_update(self, mapper, connection, instance):
|
|
methods.append('before_update')
|
|
return sa.orm.EXT_CONTINUE
|
|
|
|
def after_update(self, mapper, connection, instance):
|
|
methods.append('after_update')
|
|
return sa.orm.EXT_CONTINUE
|
|
|
|
def before_delete(self, mapper, connection, instance):
|
|
methods.append('before_delete')
|
|
return sa.orm.EXT_CONTINUE
|
|
|
|
def after_delete(self, mapper, connection, instance):
|
|
methods.append('after_delete')
|
|
return sa.orm.EXT_CONTINUE
|
|
|
|
return Ext, methods
|
|
|
|
def test_basic(self):
|
|
"""test that common user-defined methods get called."""
|
|
|
|
User, users = self.classes.User, self.tables.users
|
|
|
|
Ext, methods = self.extension()
|
|
|
|
mapper(User, users, extension=Ext())
|
|
sess = create_session()
|
|
u = User(name='u1')
|
|
sess.add(u)
|
|
sess.flush()
|
|
u = sess.query(User).populate_existing().get(u.id)
|
|
sess.expunge_all()
|
|
u = sess.query(User).get(u.id)
|
|
u.name = 'u1 changed'
|
|
sess.flush()
|
|
sess.delete(u)
|
|
sess.flush()
|
|
eq_(methods,
|
|
['instrument_class', 'init_instance', 'before_insert',
|
|
'after_insert',
|
|
'reconstruct_instance',
|
|
'before_update', 'after_update', 'before_delete', 'after_delete'])
|
|
|
|
def test_inheritance(self):
|
|
users, addresses, User = (self.tables.users,
|
|
self.tables.addresses,
|
|
self.classes.User)
|
|
|
|
Ext, methods = self.extension()
|
|
|
|
class AdminUser(User):
|
|
pass
|
|
|
|
mapper(User, users, extension=Ext())
|
|
mapper(AdminUser, addresses, inherits=User,
|
|
properties={'address_id': addresses.c.id})
|
|
|
|
sess = create_session()
|
|
am = AdminUser(name='au1', email_address='au1@e1')
|
|
sess.add(am)
|
|
sess.flush()
|
|
am = sess.query(AdminUser).populate_existing().get(am.id)
|
|
sess.expunge_all()
|
|
am = sess.query(AdminUser).get(am.id)
|
|
am.name = 'au1 changed'
|
|
sess.flush()
|
|
sess.delete(am)
|
|
sess.flush()
|
|
eq_(methods,
|
|
['instrument_class', 'instrument_class', 'init_instance',
|
|
'before_insert', 'after_insert',
|
|
'reconstruct_instance',
|
|
'before_update', 'after_update', 'before_delete',
|
|
'after_delete'])
|
|
|
|
def test_before_after_only_collection(self):
|
|
"""before_update is called on parent for collection modifications,
|
|
after_update is called even if no columns were updated.
|
|
|
|
"""
|
|
|
|
keywords, items, item_keywords, Keyword, Item = (
|
|
self.tables.keywords,
|
|
self.tables.items,
|
|
self.tables.item_keywords,
|
|
self.classes.Keyword,
|
|
self.classes.Item)
|
|
|
|
Ext1, methods1 = self.extension()
|
|
Ext2, methods2 = self.extension()
|
|
|
|
mapper(Item, items, extension=Ext1(), properties={
|
|
'keywords': relationship(Keyword, secondary=item_keywords)})
|
|
mapper(Keyword, keywords, extension=Ext2())
|
|
|
|
sess = create_session()
|
|
i1 = Item(description="i1")
|
|
k1 = Keyword(name="k1")
|
|
sess.add(i1)
|
|
sess.add(k1)
|
|
sess.flush()
|
|
eq_(methods1,
|
|
['instrument_class', 'init_instance',
|
|
'before_insert', 'after_insert'])
|
|
eq_(methods2,
|
|
['instrument_class', 'init_instance',
|
|
'before_insert', 'after_insert'])
|
|
|
|
del methods1[:]
|
|
del methods2[:]
|
|
i1.keywords.append(k1)
|
|
sess.flush()
|
|
eq_(methods1, ['before_update', 'after_update'])
|
|
eq_(methods2, [])
|
|
|
|
def test_inheritance_with_dupes(self):
|
|
"""Inheritance with the same extension instance on both mappers."""
|
|
|
|
users, addresses, User = (self.tables.users,
|
|
self.tables.addresses,
|
|
self.classes.User)
|
|
|
|
Ext, methods = self.extension()
|
|
|
|
class AdminUser(User):
|
|
pass
|
|
|
|
ext = Ext()
|
|
mapper(User, users, extension=ext)
|
|
mapper(AdminUser, addresses, inherits=User, extension=ext,
|
|
properties={'address_id': addresses.c.id})
|
|
|
|
sess = create_session()
|
|
am = AdminUser(name="au1", email_address="au1@e1")
|
|
sess.add(am)
|
|
sess.flush()
|
|
am = sess.query(AdminUser).populate_existing().get(am.id)
|
|
sess.expunge_all()
|
|
am = sess.query(AdminUser).get(am.id)
|
|
am.name = 'au1 changed'
|
|
sess.flush()
|
|
sess.delete(am)
|
|
sess.flush()
|
|
eq_(methods,
|
|
['instrument_class', 'instrument_class', 'init_instance',
|
|
'before_insert', 'after_insert',
|
|
'reconstruct_instance',
|
|
'before_update', 'after_update', 'before_delete',
|
|
'after_delete'])
|
|
|
|
def test_unnecessary_methods_not_evented(self):
|
|
users = self.tables.users
|
|
|
|
class MyExtension(sa.orm.MapperExtension):
|
|
|
|
def before_insert(self, mapper, connection, instance):
|
|
pass
|
|
|
|
class Foo(object):
|
|
pass
|
|
m = mapper(Foo, users, extension=MyExtension())
|
|
assert not m.class_manager.dispatch.load
|
|
assert not m.dispatch.before_update
|
|
assert len(m.dispatch.before_insert) == 1
|
|
|
|
|
|
class AttributeExtensionTest(fixtures.MappedTest):
|
|
|
|
@classmethod
|
|
def define_tables(cls, metadata):
|
|
Table('t1',
|
|
metadata,
|
|
Column('id', Integer, primary_key=True),
|
|
Column('type', String(40)),
|
|
Column('data', String(50))
|
|
|
|
)
|
|
|
|
def test_cascading_extensions(self):
|
|
t1 = self.tables.t1
|
|
|
|
ext_msg = []
|
|
|
|
class Ex1(sa.orm.AttributeExtension):
|
|
|
|
def set(self, state, value, oldvalue, initiator):
|
|
ext_msg.append("Ex1 %r" % value)
|
|
return "ex1" + value
|
|
|
|
class Ex2(sa.orm.AttributeExtension):
|
|
|
|
def set(self, state, value, oldvalue, initiator):
|
|
ext_msg.append("Ex2 %r" % value)
|
|
return "ex2" + value
|
|
|
|
class A(fixtures.BasicEntity):
|
|
pass
|
|
|
|
class B(A):
|
|
pass
|
|
|
|
class C(B):
|
|
pass
|
|
|
|
mapper(
|
|
A, t1, polymorphic_on=t1.c.type, polymorphic_identity='a',
|
|
properties={
|
|
'data': column_property(t1.c.data, extension=Ex1())
|
|
}
|
|
)
|
|
mapper(B, polymorphic_identity='b', inherits=A)
|
|
mapper(C, polymorphic_identity='c', inherits=B, properties={
|
|
'data': column_property(t1.c.data, extension=Ex2())
|
|
})
|
|
|
|
a1 = A(data='a1')
|
|
b1 = B(data='b1')
|
|
c1 = C(data='c1')
|
|
|
|
eq_(a1.data, 'ex1a1')
|
|
eq_(b1.data, 'ex1b1')
|
|
eq_(c1.data, 'ex2c1')
|
|
|
|
a1.data = 'a2'
|
|
b1.data = 'b2'
|
|
c1.data = 'c2'
|
|
eq_(a1.data, 'ex1a2')
|
|
eq_(b1.data, 'ex1b2')
|
|
eq_(c1.data, 'ex2c2')
|
|
|
|
eq_(ext_msg, ["Ex1 'a1'", "Ex1 'b1'", "Ex2 'c1'",
|
|
"Ex1 'a2'", "Ex1 'b2'", "Ex2 'c2'"])
|
|
|
|
|
|
class SessionExtensionTest(_fixtures.FixtureTest):
|
|
run_inserts = None
|
|
|
|
def test_extension(self):
|
|
User, users = self.classes.User, self.tables.users
|
|
|
|
mapper(User, users)
|
|
log = []
|
|
|
|
class MyExt(sa.orm.session.SessionExtension):
|
|
|
|
def before_commit(self, session):
|
|
log.append('before_commit')
|
|
|
|
def after_commit(self, session):
|
|
log.append('after_commit')
|
|
|
|
def after_rollback(self, session):
|
|
log.append('after_rollback')
|
|
|
|
def before_flush(self, session, flush_context, objects):
|
|
log.append('before_flush')
|
|
|
|
def after_flush(self, session, flush_context):
|
|
log.append('after_flush')
|
|
|
|
def after_flush_postexec(self, session, flush_context):
|
|
log.append('after_flush_postexec')
|
|
|
|
def after_begin(self, session, transaction, connection):
|
|
log.append('after_begin')
|
|
|
|
def after_attach(self, session, instance):
|
|
log.append('after_attach')
|
|
|
|
def after_bulk_update(
|
|
self,
|
|
session, query, query_context, result
|
|
):
|
|
log.append('after_bulk_update')
|
|
|
|
def after_bulk_delete(
|
|
self,
|
|
session, query, query_context, result
|
|
):
|
|
log.append('after_bulk_delete')
|
|
|
|
sess = create_session(extension=MyExt())
|
|
u = User(name='u1')
|
|
sess.add(u)
|
|
sess.flush()
|
|
assert log == [
|
|
'after_attach',
|
|
'before_flush',
|
|
'after_begin',
|
|
'after_flush',
|
|
'after_flush_postexec',
|
|
'before_commit',
|
|
'after_commit',
|
|
]
|
|
log = []
|
|
sess = create_session(autocommit=False, extension=MyExt())
|
|
u = User(name='u1')
|
|
sess.add(u)
|
|
sess.flush()
|
|
assert log == ['after_attach', 'before_flush', 'after_begin',
|
|
'after_flush', 'after_flush_postexec']
|
|
log = []
|
|
u.name = 'ed'
|
|
sess.commit()
|
|
assert log == ['before_commit', 'before_flush', 'after_flush',
|
|
'after_flush_postexec', 'after_commit']
|
|
log = []
|
|
sess.commit()
|
|
assert log == ['before_commit', 'after_commit']
|
|
log = []
|
|
sess.query(User).delete()
|
|
assert log == ['after_begin', 'after_bulk_delete']
|
|
log = []
|
|
sess.query(User).update({'name': 'foo'})
|
|
assert log == ['after_bulk_update']
|
|
log = []
|
|
sess = create_session(autocommit=False, extension=MyExt(),
|
|
bind=testing.db)
|
|
sess.connection()
|
|
assert log == ['after_begin']
|
|
sess.close()
|
|
|
|
def test_multiple_extensions(self):
|
|
User, users = self.classes.User, self.tables.users
|
|
|
|
log = []
|
|
|
|
class MyExt1(sa.orm.session.SessionExtension):
|
|
|
|
def before_commit(self, session):
|
|
log.append('before_commit_one')
|
|
|
|
class MyExt2(sa.orm.session.SessionExtension):
|
|
|
|
def before_commit(self, session):
|
|
log.append('before_commit_two')
|
|
|
|
mapper(User, users)
|
|
sess = create_session(extension=[MyExt1(), MyExt2()])
|
|
u = User(name='u1')
|
|
sess.add(u)
|
|
sess.flush()
|
|
assert log == [
|
|
'before_commit_one',
|
|
'before_commit_two',
|
|
]
|
|
|
|
def test_unnecessary_methods_not_evented(self):
|
|
class MyExtension(sa.orm.session.SessionExtension):
|
|
|
|
def before_commit(self, session):
|
|
pass
|
|
|
|
s = Session(extension=MyExtension())
|
|
assert not s.dispatch.after_commit
|
|
assert len(s.dispatch.before_commit) == 1
|
|
|
|
|
|
class QueryEventsTest(
|
|
_RemoveListeners, _fixtures.FixtureTest, AssertsCompiledSQL):
|
|
__dialect__ = 'default'
|
|
|
|
@classmethod
|
|
def setup_mappers(cls):
|
|
User = cls.classes.User
|
|
users = cls.tables.users
|
|
|
|
mapper(User, users)
|
|
|
|
def test_before_compile(self):
|
|
@event.listens_for(query.Query, "before_compile", retval=True)
|
|
def no_deleted(query):
|
|
for desc in query.column_descriptions:
|
|
if desc['type'] is User:
|
|
entity = desc['expr']
|
|
query = query.filter(entity.id != 10)
|
|
return query
|
|
|
|
User = self.classes.User
|
|
s = Session()
|
|
|
|
q = s.query(User).filter_by(id=7)
|
|
self.assert_compile(
|
|
q,
|
|
"SELECT users.id AS users_id, users.name AS users_name "
|
|
"FROM users "
|
|
"WHERE users.id = :id_1 AND users.id != :id_2",
|
|
checkparams={'id_2': 10, 'id_1': 7}
|
|
)
|
|
|
|
def test_alters_entities(self):
|
|
User = self.classes.User
|
|
|
|
@event.listens_for(query.Query, "before_compile", retval=True)
|
|
def fn(query):
|
|
return query.add_columns(User.name)
|
|
|
|
s = Session()
|
|
|
|
q = s.query(User.id, ).filter_by(id=7)
|
|
self.assert_compile(
|
|
q,
|
|
"SELECT users.id AS users_id, users.name AS users_name "
|
|
"FROM users "
|
|
"WHERE users.id = :id_1",
|
|
checkparams={'id_1': 7}
|
|
)
|
|
eq_(
|
|
q.all(),
|
|
[(7, 'jack')]
|
|
)
|
|
|
|
|
|
class RefreshFlushInReturningTest(fixtures.MappedTest):
|
|
"""test [ticket:3427].
|
|
|
|
this is a rework of the test for [ticket:3167] stated
|
|
in test_unitofworkv2, which tests that returning doesn't trigger
|
|
attribute events; the test here is *reversed* so that we test that
|
|
it *does* trigger the new refresh_flush event.
|
|
|
|
"""
|
|
|
|
__backend__ = True
|
|
|
|
@classmethod
|
|
def define_tables(cls, metadata):
|
|
Table(
|
|
'test', metadata,
|
|
Column('id', Integer, primary_key=True,
|
|
test_needs_autoincrement=True),
|
|
Column('prefetch_val', Integer, default=5),
|
|
Column('returning_val', Integer, server_default="5")
|
|
)
|
|
|
|
@classmethod
|
|
def setup_classes(cls):
|
|
class Thing(cls.Basic):
|
|
pass
|
|
|
|
@classmethod
|
|
def setup_mappers(cls):
|
|
Thing = cls.classes.Thing
|
|
|
|
mapper(Thing, cls.tables.test, eager_defaults=True)
|
|
|
|
def test_no_attr_events_flush(self):
|
|
Thing = self.classes.Thing
|
|
mock = Mock()
|
|
event.listen(Thing, "refresh_flush", mock)
|
|
t1 = Thing()
|
|
s = Session()
|
|
s.add(t1)
|
|
s.flush()
|
|
|
|
if testing.requires.returning.enabled:
|
|
# ordering is deterministic in this test b.c. the routine
|
|
# appends the "returning" params before the "prefetch"
|
|
# ones. if there were more than one attribute in each category,
|
|
# then we'd have hash order issues.
|
|
eq_(
|
|
mock.mock_calls,
|
|
[call(t1, ANY, ['returning_val', 'prefetch_val'])]
|
|
)
|
|
else:
|
|
eq_(
|
|
mock.mock_calls,
|
|
[call(t1, ANY, ['prefetch_val'])]
|
|
)
|
|
|
|
eq_(t1.id, 1)
|
|
eq_(t1.prefetch_val, 5)
|
|
eq_(t1.returning_val, 5)
|