mirror of
https://github.com/GSA/notifications-api.git
synced 2026-08-14 02:48:01 -04:00
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.
99 lines
2.8 KiB
Python
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)
|