Files
sqlalchemy/test/orm/test_load_on_fks.py
T
Mike Bayer 108c60f460 - The :class:.SessionEvents suite now includes events to allow
unambiguous tracking of all object lifecycle state transitions
in terms of the :class:`.Session` itself, e.g. pending,
transient,  persistent, detached.   The state of the object
within each event is also defined.
fixes #2677
- Added a new session lifecycle state :term:`deleted`.  This new state
represents an object that has been deleted from the :term:`persistent`
state and will move to the :term:`detached` state once the transaction
is committed.  This resolves the long-standing issue that objects
which were deleted existed in a gray area between persistent and
detached.   The :attr:`.InstanceState.persistent` accessor will
**no longer** report on a deleted object as persistent; the
:attr:`.InstanceState.deleted` accessor will instead be True for
these objects, until they become detached.
- The :paramref:`.Session.weak_identity_map` parameter is deprecated.
See the new recipe at :ref:`session_referencing_behavior` for
an event-based approach to maintaining strong identity map behavior.
references #3517
2015-09-02 17:55:15 -04:00

321 lines
10 KiB
Python

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.testing import eq_, AssertsExecutionResults, assert_raises
from sqlalchemy import testing
from sqlalchemy.testing import fixtures
from sqlalchemy.orm.attributes import instance_state
from sqlalchemy.orm.exc import FlushError
from sqlalchemy.testing.schema import Table, Column
engine = testing.db
class FlushOnPendingTest(AssertsExecutionResults, fixtures.TestBase):
def setUp(self):
global Parent, Child, Base
Base= declarative_base()
class Parent(Base):
__tablename__ = 'parent'
id= Column(Integer, primary_key=True, test_needs_autoincrement=True)
name = Column(String(50), nullable=False)
children = relationship("Child", load_on_pending=True)
class Child(Base):
__tablename__ = 'child'
id= Column(Integer, primary_key=True, test_needs_autoincrement=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
Base.metadata.create_all(engine)
def tearDown(self):
Base.metadata.drop_all(engine)
def test_annoying_autoflush_one(self):
sess = Session(engine)
p1 = Parent()
sess.add(p1)
p1.children = []
def test_annoying_autoflush_two(self):
sess = Session(engine)
p1 = Parent()
sess.add(p1)
assert p1.children == []
def test_dont_load_if_no_keys(self):
sess = Session(engine)
p1 = Parent()
sess.add(p1)
def go():
assert p1.children == []
self.assert_sql_count(testing.db, go, 0)
class LoadOnFKsTest(AssertsExecutionResults, fixtures.TestBase):
def setUp(self):
global Parent, Child, Base
Base= declarative_base()
class Parent(Base):
__tablename__ = 'parent'
__table_args__ = {'mysql_engine':'InnoDB'}
id= Column(Integer, primary_key=True, test_needs_autoincrement=True)
class Child(Base):
__tablename__ = 'child'
__table_args__ = {'mysql_engine':'InnoDB'}
id= Column(Integer, primary_key=True, test_needs_autoincrement=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
parent = relationship(Parent, backref=backref("children"))
Base.metadata.create_all(engine)
global sess, p1, p2, c1, c2
sess = Session(bind=engine)
p1 = Parent()
p2 = Parent()
c1, c2 = Child(), Child()
c1.parent = p1
sess.add_all([p1, p2])
assert c1 in sess
sess.commit()
def tearDown(self):
sess.rollback()
Base.metadata.drop_all(engine)
def test_load_on_pending_disallows_backref_event(self):
Child.parent.property.load_on_pending = True
sess.autoflush = False
c3 = Child()
sess.add(c3)
c3.parent_id = p1.id
c3.parent = p1
# a side effect of load-on-pending with no autoflush.
# a change to the backref event handler to check
# collection membership before assuming "old == new so return"
# would fix this - but this is wasteful and autoflush
# should be turned on.
assert c3 not in p1.children
def test_enable_rel_loading_disallows_backref_event(self):
sess.autoflush = False
c3 = Child()
sess.enable_relationship_loading(c3)
c3.parent_id = p1.id
c3.parent = p1
# c3.parent is already acting like a "load" here,
# so backref events don't work
assert c3 not in p1.children
def test_load_on_persistent_allows_backref_event(self):
Child.parent.property.load_on_pending = True
c3 = Child()
sess.add(c3)
c3.parent_id = p1.id
c3.parent = p1
assert c3 in p1.children
def test_enable_rel_loading_on_persistent_disallows_backref_event(self):
c3 = Child()
sess.enable_relationship_loading(c3)
c3.parent_id = p1.id
c3.parent = p1
# c3.parent is already acting like a "load" here,
# so backref events don't work
assert c3 not in p1.children
def test_no_load_on_pending_allows_backref_event(self):
# users who stick with the program and don't use
# 'load_on_pending' get expected behavior
sess.autoflush = False
c3 = Child()
sess.add(c3)
c3.parent_id = p1.id
c3.parent = p1
assert c3 in p1.children
def test_autoflush_on_pending(self):
c3 = Child()
sess.add(c3)
c3.parent_id = p1.id
# pendings don't autoflush
assert c3.parent is None
def test_autoflush_on_pending(self):
Child.parent.property.load_on_pending = True
c3 = Child()
sess.add(c3)
c3.parent_id = p1.id
# ...unless the flag is on
assert c3.parent is p1
def test_collection_load_from_pending_populated(self):
Parent.children.property.load_on_pending = True
p2 = Parent(id=p1.id)
sess.add(p2)
# load should emit since PK is populated
def go():
assert p2.children
self.assert_sql_count(testing.db, go, 1)
def test_collection_load_from_pending_no_sql(self):
Parent.children.property.load_on_pending = True
p2 = Parent(id=None)
sess.add(p2)
# load should not emit since "None" is the bound
# param list
def go():
assert not p2.children
self.assert_sql_count(testing.db, go, 0)
def test_load_on_pending_with_set(self):
Child.parent.property.load_on_pending = True
p1.children
c3 = Child()
sess.add(c3)
c3.parent_id = p1.id
def go():
c3.parent = p1
self.assert_sql_count(testing.db, go, 0)
def test_backref_doesnt_double(self):
Child.parent.property.load_on_pending = True
sess.autoflush = False
p1.children
c3 = Child()
sess.add(c3)
c3.parent = p1
c3.parent = p1
c3.parent = p1
c3.parent = p1
assert len(p1.children)== 2
def test_m2o_lazy_loader_on_persistent(self):
"""Compare the behaviors from the lazyloader using
the "committed" state in all cases, vs. the lazyloader
using the "current" state in all cases except during flush.
"""
for loadfk in (True, False):
for loadrel in (True, False):
for autoflush in (True, False):
for manualflush in (True, False):
for fake_autoexpire in (True, False):
sess.autoflush = autoflush
if loadfk:
c1.parent_id
if loadrel:
c1.parent
c1.parent_id = p2.id
if manualflush:
sess.flush()
# fake_autoexpire refers to the eventual
# auto-expire of 'parent' when c1.parent_id
# is altered.
if fake_autoexpire:
sess.expire(c1, ['parent'])
# old 0.6 behavior
#if manualflush and (not loadrel or fake_autoexpire):
# # a flush occurs, we get p2
# assert c1.parent is p2
#elif not loadrel and not loadfk:
# # problematically - we get None since committed state
# # is empty when c1.parent_id was mutated, since we want
# # to save on selects. this is
# # why the patch goes in in 0.6 - this is mostly a bug.
# assert c1.parent is None
#else:
# # if things were loaded, autoflush doesn't even
# # happen.
# assert c1.parent is p1
# new behavior
if loadrel and not fake_autoexpire:
assert c1.parent is p1
else:
assert c1.parent is p2
sess.rollback()
def test_m2o_lazy_loader_on_pending(self):
for loadonpending in (False, True):
for autoflush in (False, True):
for manualflush in (False, True):
Child.parent.property.load_on_pending = loadonpending
sess.autoflush = autoflush
c2 = Child()
sess.add(c2)
c2.parent_id = p2.id
if manualflush:
sess.flush()
if loadonpending or manualflush:
assert c2.parent is p2
else:
assert c2.parent is None
sess.rollback()
def test_m2o_lazy_loader_on_transient(self):
for loadonpending in (False, True):
for attach in (False, True):
for autoflush in (False, True):
for manualflush in (False, True):
for enable_relationship_rel in (False, True):
Child.parent.property.load_on_pending = loadonpending
sess.autoflush = autoflush
c2 = Child()
if attach:
state = instance_state(c2)
state.session_id = sess.hash_key
if enable_relationship_rel:
sess.enable_relationship_loading(c2)
c2.parent_id = p2.id
if manualflush:
sess.flush()
if (loadonpending and attach) or enable_relationship_rel:
assert c2.parent is p2
else:
assert c2.parent is None
sess.rollback()