summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--addons/mail/models/mail_thread.py11
-rw-r--r--addons/mail/tests/common.py2
-rw-r--r--odoo/addons/base/tests/test_misc.py56
-rw-r--r--odoo/http.py2
-rw-r--r--odoo/sql_db.py34
-rw-r--r--odoo/tests/common.py12
-rw-r--r--odoo/tools/misc.py96
7 files changed, 118 insertions, 95 deletions
diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py
index 24adb15b450..802fef140ae 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -516,8 +516,8 @@ class MailThread(models.AbstractModel):
fnames = self._get_tracked_fields().intersection(fields)
if not fnames:
return
- func = self.browse()._finalize_tracking
- [initial_values] = self.env.cr.precommit.add(func, dict)
+ self.env.cr.precommit.add(self._finalize_tracking)
+ initial_values = self.env.cr.precommit.data.setdefault(f'mail.tracking.{self._name}', {})
for record in self:
if not record.id:
continue
@@ -530,16 +530,17 @@ class MailThread(models.AbstractModel):
""" Prevent any tracking of fields on ``self``. """
if not self._get_tracked_fields():
return
- func = self.browse()._finalize_tracking
- [initial_values] = self.env.cr.precommit.add(func, dict)
+ self.env.cr.precommit.add(self._finalize_tracking)
+ initial_values = self.env.cr.precommit.data.setdefault(f'mail.tracking.{self._name}', {})
# disable tracking by setting initial values to None
for id_ in self.ids:
initial_values[id_] = None
- def _finalize_tracking(self, initial_values):
+ def _finalize_tracking(self):
""" Generate the tracking messages for the records that have been
prepared with ``_prepare_tracking``.
"""
+ initial_values = self.env.cr.precommit.data.pop(f'mail.tracking.{self._name}', {})
ids = [id_ for id_, vals in initial_values.items() if vals]
if not ids:
return
diff --git a/addons/mail/tests/common.py b/addons/mail/tests/common.py
index b20794ba8f7..84b17c9c080 100644
--- a/addons/mail/tests/common.py
+++ b/addons/mail/tests/common.py
@@ -334,7 +334,7 @@ class MailCase(MockEmail):
def flush_tracking(self):
""" Force the creation of tracking values. """
self.env['base'].flush()
- self.cr.precommit()
+ self.cr.precommit.run()
# ------------------------------------------------------------
# MAIL MOCKS
diff --git a/odoo/addons/base/tests/test_misc.py b/odoo/addons/base/tests/test_misc.py
index 2cf2ebc7842..0c25a5e1669 100644
--- a/odoo/addons/base/tests/test_misc.py
+++ b/odoo/addons/base/tests/test_misc.py
@@ -272,36 +272,54 @@ class TestFormatLangDate(TransactionCase):
self.assertEqual(misc.format_time(lang.with_context(lang='zh_CN').env, time_part, time_format='medium', lang_code='fr_FR'), '16:30:22')
-class TestGroupCalls(BaseCase):
- def test_callbacks(self):
+class TestCallbacks(BaseCase):
+ def test_callback(self):
log = []
+ callbacks = misc.Callbacks()
+ # add foo
def foo():
log.append("foo")
- def bar(items):
- log.extend(items)
- callbacks.add(baz)
+ callbacks.add(foo)
- def baz():
- log.append("baz")
+ # add bar
+ @callbacks.add
+ def bar():
+ log.append("bar")
- callbacks = misc.GroupCalls()
+ # add foo again
callbacks.add(foo)
- callbacks.add(bar, list)[0].append(1)
- callbacks.add(bar, list)[0].append(2)
- self.assertEqual(log, [])
- callbacks()
- self.assertEqual(log, ["foo", 1, 2, "baz"])
+ # this should call foo(), bar(), foo()
+ callbacks.run()
+ self.assertEqual(log, ["foo", "bar", "foo"])
+
+ # this should do nothing
+ callbacks.run()
+ self.assertEqual(log, ["foo", "bar", "foo"])
+
+ def test_aggregate(self):
+ log = []
+ callbacks = misc.Callbacks()
+
+ # register foo once
+ @callbacks.add
+ def foo():
+ log.append(callbacks.data["foo"])
+
+ # aggregate data
+ callbacks.data.setdefault("foo", []).append(1)
+ callbacks.data.setdefault("foo", []).append(2)
+ callbacks.data.setdefault("foo", []).append(3)
- callbacks()
- self.assertEqual(log, ["foo", 1, 2, "baz"])
+ # foo() is called once
+ callbacks.run()
+ self.assertEqual(log, [[1, 2, 3]])
+ self.assertFalse(callbacks.data)
- callbacks.add(bar, list)[0].append(3)
- callbacks.clear()
- callbacks()
- self.assertEqual(log, ["foo", 1, 2, "baz"])
+ callbacks.run()
+ self.assertEqual(log, [[1, 2, 3]])
class TestRemoveAccents(BaseCase):
diff --git a/odoo/http.py b/odoo/http.py
index 7006d6313b9..419e34e9f81 100644
--- a/odoo/http.py
+++ b/odoo/http.py
@@ -351,7 +351,7 @@ class WebRequest(object):
# flush here to avoid triggering a serialization error outside
# of this context, which would not retry the call
flush_env(self._cr)
- self._cr.precommit()
+ self._cr.precommit.run()
return result
if self.db:
diff --git a/odoo/sql_db.py b/odoo/sql_db.py
index 18f3633539d..e8426691cd1 100644
--- a/odoo/sql_db.py
+++ b/odoo/sql_db.py
@@ -96,13 +96,11 @@ def check(f, self, *args, **kwargs):
class BaseCursor:
- """ Base class for cursors that manages pre/post commit/rollback hooks. """
+ """ Base class for cursors that manage pre/post commit hooks. """
def __init__(self):
- self.precommit = tools.GroupCalls()
- self.postcommit = tools.GroupCalls()
- self.prerollback = tools.GroupCalls()
- self.postrollback = tools.GroupCalls()
+ self.precommit = tools.Callbacks()
+ self.postcommit = tools.Callbacks()
@contextmanager
@check
@@ -111,20 +109,17 @@ class BaseCursor:
name = uuid.uuid1().hex
if flush:
flush_env(self, clear=False)
- self.precommit()
- self.prerollback.clear()
+ self.precommit.run()
self.execute('SAVEPOINT "%s"' % name)
try:
yield
if flush:
flush_env(self, clear=False)
- self.precommit()
- self.prerollback.clear()
+ self.precommit.run()
except Exception:
if flush:
clear_env(self)
self.precommit.clear()
- self.prerollback()
self.execute('ROLLBACK TO SAVEPOINT "%s"' % name)
raise
else:
@@ -428,17 +423,15 @@ class Cursor(BaseCursor):
if event == 'commit':
self.postcommit.add(func)
elif event == 'rollback':
- self.postrollback.add(func)
+ raise NotImplementedError()
@check
def commit(self):
""" Perform an SQL `COMMIT` """
flush_env(self)
- self.precommit()
+ self.precommit.run()
result = self._cnx.commit()
- self.prerollback.clear()
- self.postrollback.clear()
- self.postcommit()
+ self.postcommit.run()
return result
@check
@@ -447,9 +440,7 @@ class Cursor(BaseCursor):
clear_env(self)
self.precommit.clear()
self.postcommit.clear()
- self.prerollback()
result = self._cnx.rollback()
- self.postrollback()
return result
@check
@@ -506,23 +497,18 @@ class TestCursor(BaseCursor):
def commit(self):
""" Perform an SQL `COMMIT` """
flush_env(self)
- self.precommit()
+ self.precommit.run()
self._cursor.execute('SAVEPOINT "%s"' % self._savepoint)
- self.prerollback.clear()
- # ignore post-commit/rollback hooks
+ # ignore post-commit hooks
self.postcommit.clear()
- self.postrollback.clear()
@check
def rollback(self):
""" Perform an SQL `ROLLBACK` """
clear_env(self)
self.precommit.clear()
- self.prerollback()
self._cursor.execute('ROLLBACK TO SAVEPOINT "%s"' % self._savepoint)
- # ignore post-commit/rollback hooks
self.postcommit.clear()
- self.postrollback.clear()
def __getattr__(self, name):
value = getattr(self._cursor, name)
diff --git a/odoo/tests/common.py b/odoo/tests/common.py
index a4ad9942613..01318a64422 100644
--- a/odoo/tests/common.py
+++ b/odoo/tests/common.py
@@ -398,14 +398,14 @@ class BaseCase(TreeCase, MetaCase('DummyCase', (object,), {})):
if flush:
self.env.user.flush()
- self.env.cr.precommit()
+ self.env.cr.precommit.run()
with patch('odoo.sql_db.Cursor.execute', execute):
with patch('odoo.osv.expression.get_unaccent_wrapper', get_unaccent_wrapper):
yield actual_queries
if flush:
self.env.user.flush()
- self.env.cr.precommit()
+ self.env.cr.precommit.run()
self.assertEqual(
len(actual_queries), len(expected),
@@ -438,12 +438,12 @@ class BaseCase(TreeCase, MetaCase('DummyCase', (object,), {})):
expected = counters.get(login, default)
if flush:
self.env.user.flush()
- self.env.cr.precommit()
+ self.env.cr.precommit.run()
count0 = self.cr.sql_log_count
yield
if flush:
self.env.user.flush()
- self.env.cr.precommit()
+ self.env.cr.precommit.run()
count = self.cr.sql_log_count - count0
if count != expected:
# add some info on caller to allow semi-automatic update of query count
@@ -462,11 +462,11 @@ class BaseCase(TreeCase, MetaCase('DummyCase', (object,), {})):
# same operations, otherwise the caches might not be ready!
if flush:
self.env.user.flush()
- self.env.cr.precommit()
+ self.env.cr.precommit.run()
yield
if flush:
self.env.user.flush()
- self.env.cr.precommit()
+ self.env.cr.precommit.run()
def assertRecordValues(self, records, expected_values):
''' Compare a recordset with a list of dictionaries representing the expected results.
diff --git a/odoo/tools/misc.py b/odoo/tools/misc.py
index 31e391fe30e..5173e046467 100644
--- a/odoo/tools/misc.py
+++ b/odoo/tools/misc.py
@@ -1084,54 +1084,72 @@ class LastOrderedSet(OrderedSet):
OrderedSet.add(self, elem)
-class GroupCalls:
- """ A collection of callbacks with support for aggregated arguments. Upon
- call, every registered function is called once with positional arguments.
- When registering a function, a tuple of positional arguments is returned, so
- that the caller can modify the arguments in place. This allows to
- accumulate some data to process once::
+class Callbacks:
+ """ A simple queue of callback functions. Upon run, every function is
+ called (in addition order), and the queue is emptied.
- callbacks = GroupCalls()
+ callbacks = Callbacks()
- # register print (by default with a list)
- [args] = callbacks.register(print, list)
- args.append(42)
+ # add foo
+ def foo():
+ print("foo")
- # add an element to the list to print
- [args] = callbacks.register(print, list)
- args.append(43)
+ callbacks.add(foo)
- # print "[42, 43]"
- callbacks()
+ # add bar
+ callbacks.add
+ def bar():
+ print("bar")
+
+ # add foo again
+ callbacks.add(foo)
+
+ # call foo(), bar(), foo(), then clear the callback queue
+ callbacks.run()
+
+ The queue also provides a ``data`` dictionary, that may be freely used to
+ store anything, but is mostly aimed at aggregating data for callbacks. The
+ dictionary is automatically cleared by ``run()`` once all callback functions
+ have been called.
+
+ # register foo to process aggregated data
+ @callbacks.add
+ def foo():
+ print(sum(callbacks.data['foo']))
+
+ callbacks.data.setdefault('foo', []).append(1)
+ ...
+ callbacks.data.setdefault('foo', []).append(2)
+ ...
+ callbacks.data.setdefault('foo', []).append(3)
+
+ # call foo(), which prints 6
+ callbacks.run()
+
+ Given the global nature of ``data``, the keys should identify in a unique
+ way the data being stored. It is recommended to use strings with a
+ structure like ``"{module}.{feature}"``.
"""
+ __slots__ = ['_funcs', 'data']
+
def __init__(self):
- self._func_args = {} # {func: args}
+ self._funcs = []
+ self.data = {}
- def __call__(self):
- """ Call all the registered functions (in first addition order) with
- their respective arguments. Only recurrent functions remain registered
- after the call.
- """
- func_args = self._func_args
- while func_args:
- func = next(iter(func_args))
- args = func_args.pop(func)
- func(*args)
-
- def add(self, func, *types):
- """ Register the given function, and return the tuple of positional
- arguments to call the function with. If the function is not registered
- yet, the list of arguments is made up by invoking the given types.
- """
- try:
- return self._func_args[func]
- except KeyError:
- args = self._func_args[func] = [type_() for type_ in types]
- return args
+ def add(self, func):
+ """ Add the given function. """
+ self._funcs.append(func)
+
+ def run(self):
+ """ Call all the functions (in addition order), then clear. """
+ for func in self._funcs:
+ func()
+ self.clear()
def clear(self):
- """ Remove all callbacks from self. """
- self._func_args.clear()
+ """ Remove all callbacks and data from self. """
+ self._funcs.clear()
+ self.data.clear()
class IterableGenerator: