Skip to content

Commit 43e2c36

Browse files
authored
Merge pull request #6 from charness/master
Expire session state as at the top level
2 parents 55eed1c + 4a5c94e commit 43e2c36

File tree

3 files changed

+78
-0
lines changed

3 files changed

+78
-0
lines changed

pytest_flask_sqlalchemy/fixtures.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ def _transaction(request, _db, mocker):
5353
@sa.event.listens_for(session, 'after_transaction_end')
5454
def restart_savepoint(session, trans):
5555
if trans.nested and not trans._parent.nested:
56+
# ensure that state is expired the way
57+
# session.commit() at the top level normally does
58+
session.expire_all()
59+
5660
session.begin_nested()
5761

5862
# Force the connection to use nested transactions

tests/_conftest.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,35 @@ def drop_tables():
7979
_db.drop_all()
8080

8181
return Person
82+
83+
84+
@pytest.fixture(scope='module')
85+
def account_address(request, _db, person):
86+
'''
87+
Create tables to use for testing deletes and relationships.
88+
'''
89+
class Account(_db.Model):
90+
__tablename__ = 'account'
91+
92+
id = _db.Column(_db.Integer, primary_key=True)
93+
addresses = _db.relationship(
94+
'Address',
95+
back_populates='account',
96+
)
97+
98+
class Address(_db.Model):
99+
__tablename__ = 'address'
100+
101+
id = _db.Column(_db.Integer, primary_key=True)
102+
103+
account_id = _db.Column(_db.Integer, _db.ForeignKey('account.id'))
104+
account = _db.relationship('Account', back_populates='addresses')
105+
106+
# Create tables
107+
_db.create_all()
108+
109+
@request.addfinalizer
110+
def drop_tables():
111+
_db.drop_all()
112+
113+
return Account, Address

tests/test_fixtures.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,3 +294,45 @@ def test_raw_connection_changes_dont_persist(person, db_engine, db_session):
294294

295295
result = db_testdir.runpytest()
296296
result.assert_outcomes(passed=2)
297+
298+
299+
def test_commit_works_with_deleted_dependent(db_testdir):
300+
'''
301+
Make sure a commit still works with a dangling reference to a
302+
deleted instance.
303+
304+
Exercise one way to trigger the issue reported in
305+
https://github.com/jeancochrane/pytest-flask-sqlalchemy/issues/5
306+
'''
307+
db_testdir.makepyfile("""
308+
def test_delete_message(account_address, db_session):
309+
account, address = account_address
310+
311+
# Create a new object instance using the ORM
312+
account_inst = account(id=1)
313+
314+
db_session.add(account_inst)
315+
316+
# Create a dependent object instance using the ORM
317+
318+
address_inst = address(id=101, account_id=1)
319+
320+
db_session.add(address_inst)
321+
db_session.commit()
322+
323+
# Access the address through an ORM attribute
324+
assert account_inst.addresses
325+
326+
# Delete the address out from under account_inst and the ORM
327+
db_session.delete(address_inst)
328+
329+
# shouldn't see an exception like
330+
# sqlalchemy.exc.InvalidRequestError:
331+
# Instance XXX has been deleted.
332+
# Use the make_transient() function to send
333+
# this object back to the transient state.
334+
db_session.commit()
335+
""")
336+
337+
result = db_testdir.runpytest()
338+
result.assert_outcomes(passed=1)

0 commit comments

Comments
 (0)