Files
notifications-api/app/dao/dao_utils.py
Rebecca Law 6704be4021 Adding @nested_transactional for transactions that require more than one
db update/insert.

Using a savepoint for the multiple transactions allows us to rollback if
there is an error when executing the second db transaction.
However, this does add a bit of complexity. Developers need to manage
the db session when calling multiple nested tranactions.

Unit tests have been added to test this functionality and some end to
end tests have been done to make sure all transactions are rollback if
there is an exception while executing the transaction.
2021-04-12 13:59:57 +01:00

99 lines
2.8 KiB
Python

import itertools
from functools import wraps
from app import db
from app.history_meta import create_history
def transactional(func):
@wraps(func)
def commit_or_rollback(*args, **kwargs):
try:
res = func(*args, **kwargs)
db.session.commit()
return res
except Exception:
db.session.rollback()
raise
return commit_or_rollback
def nested_transactional(func):
# This creates a save point for the nested transaction.
# You must manage the commit or rollback from outer most call of the nested of the transactions.
@wraps(func)
def commit_or_rollback(*args, **kwargs):
try:
db.session.begin_nested()
res = func(*args, **kwargs)
db.session.commit()
return res
except Exception:
db.session.rollback()
raise
return commit_or_rollback
class VersionOptions():
def __init__(self, model_class, history_class=None, must_write_history=True):
self.model_class = model_class
self.history_class = history_class
self.must_write_history = must_write_history
def version_class(*version_options):
if len(version_options) == 1 and not isinstance(version_options[0], VersionOptions):
version_options = (VersionOptions(version_options[0]),)
def versioned(func):
@wraps(func)
def record_version(*args, **kwargs):
func(*args, **kwargs)
session_objects = []
for version_option in version_options:
tmp_session_objects = [
(
session_object, version_option.history_class
)
for session_object in itertools.chain(
db.session.new, db.session.dirty
)
if isinstance(
session_object, version_option.model_class
)
]
if tmp_session_objects == [] and version_option.must_write_history:
raise RuntimeError((
'Can\'t record history for {} '
'(something in your code has casued the database to '
'flush the session early so there\'s nothing to '
'copy into the history table)'
).format(version_option.model_class.__name__))
session_objects += tmp_session_objects
for session_object, history_class in session_objects:
db.session.add(
create_history(session_object, history_cls=history_class)
)
return record_version
return versioned
def dao_rollback():
db.session.rollback()
@transactional
def dao_save_object(obj):
# add/update object in db
db.session.add(obj)