Skip to content

Commit b698ff0

Browse files
Record operations only for signalling sessions (reprise)
This is a rebase of commit cf40cae (GitHub pull request pallets-eco#109 by @paxan) on current master. The original changes were (I assume accidentally) undone in commit eb208b5 (GitHub pull request pallets-eco#123 by @thrisp). The original commit message is copied below. In some scenarios model classes derived from SQLAlchemy(...).Model may be used with basic SQLAlchemy session instances which aren't extended with signalling functionality. Before this fix, when you try using a vanilla session with such models, you would get exception like this: File ".../flask_sqlalchemy.py", line 163, in after_insert return self._record(mapper, instance, 'insert') File ".../flask_sqlalchemy.py", line 172, in _record s._model_changes[pk] = (model, operation) AttributeError: 'SessionMaker' object has no attribute '_model_changes' With this fix, this problem is resolved.
1 parent 43f8c7d commit b698ff0

File tree

2 files changed

+40
-2
lines changed

2 files changed

+40
-2
lines changed

flask_sqlalchemy.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,11 @@ def mapper_signal_after_update(self, mapper, connection, target):
219219

220220
@staticmethod
221221
def _record(mapper, target, operation):
222-
pk = tuple(mapper.primary_key_from_instance(target))
223-
orm.object_session(target)._model_changes[pk] = (target, operation)
222+
s = orm.object_session(target)
223+
if isinstance(s, _SignallingSession):
224+
pk = tuple(mapper.primary_key_from_instance(target))
225+
s._model_changes[pk] = (target, operation)
226+
224227

225228
def get_debug_queries():
226229
"""In debug mode Flask-SQLAlchemy will log all the SQL queries sent

test_sqlalchemy.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from datetime import datetime
77
import flask
88
from flask.ext import sqlalchemy
9+
from sqlalchemy.orm import sessionmaker
910

1011

1112
def make_todo_model(db):
@@ -454,6 +455,39 @@ def test_roll_back_on_failure(self):
454455
self.assertEqual(self.client.get('/').data, '')
455456

456457

458+
class StandardSessionTestCase(unittest.TestCase):
459+
def test_insert_update_delete(self):
460+
# Ensure _SignalTrackingMapperExtension doesn't croak when
461+
# faced with a vanilla SQLAlchemy session.
462+
#
463+
# Verifies that "AttributeError: 'SessionMaker' object has no attribute '_model_changes'"
464+
# is not thrown.
465+
app = flask.Flask(__name__)
466+
app.config['SQLALCHEMY_ENGINE'] = 'sqlite://'
467+
app.config['TESTING'] = True
468+
db = sqlalchemy.SQLAlchemy(app)
469+
Session = sessionmaker(bind=db.engine)
470+
471+
class QazWsx(db.Model):
472+
id = db.Column(db.Integer, primary_key=True)
473+
x = db.Column(db.String, default='')
474+
475+
db.create_all()
476+
session = Session()
477+
session.add(QazWsx())
478+
session.flush() # issues an INSERT.
479+
session.expunge_all()
480+
qaz_wsx = session.query(QazWsx).first()
481+
assert qaz_wsx.x == ''
482+
qaz_wsx.x = 'test'
483+
session.flush() # issues an UPDATE.
484+
session.expunge_all()
485+
qaz_wsx = session.query(QazWsx).first()
486+
assert qaz_wsx.x == 'test'
487+
session.delete(qaz_wsx) # issues a DELETE.
488+
assert session.query(QazWsx).first() is None
489+
490+
457491
def suite():
458492
suite = unittest.TestSuite()
459493
suite.addTest(unittest.makeSuite(BasicAppTestCase))
@@ -468,6 +502,7 @@ def suite():
468502
suite.addTest(unittest.makeSuite(CommitOnTeardownTestCase))
469503
if flask.signals_available:
470504
suite.addTest(unittest.makeSuite(SignallingTestCase))
505+
suite.addTest(unittest.makeSuite(StandardSessionTestCase))
471506
return suite
472507

473508

0 commit comments

Comments
 (0)