Skip to content

Commit 77e2cae

Browse files
Guewen Baconnierbizzappdev
authored andcommitted
Add method to patch a method to be automatically delayed
This patch method has to be called in ``_register_hook``. When a method is patched, any call to the method will not directly execute the method's body, but will instead enqueue a job. When a ``context_key`` is set when calling ``_patch_job_auto_delay``, the patched method is automatically delayed only when this key is ``True`` in the caller's context. It is advised to patch the method with a ``context_key``, because making the automatic delay *in any case* can produce nasty and unexpected side effects (e.g. another module calls the method and expects it to be computed before doing something else, expecting a result, ...). A typical use case is when a method in a module we don't control is called synchronously in the middle of another method, and we'd like all the calls to this method become asynchronous. It relies on OCA#274 that deprecates the `@job` decorator.
1 parent 3e17e3f commit 77e2cae

1 file changed

Lines changed: 92 additions & 0 deletions

File tree

queue_job/models/base.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Copyright 2016 Camptocamp
22
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
33

4+
import functools
45
import logging
56
import os
67

@@ -97,3 +98,94 @@ def with_delay(
9798
channel=channel,
9899
identity_key=identity_key,
99100
)
101+
102+
def _patch_job_auto_delay(self, method_name, context_key=None):
103+
"""Patch a method to be automatically delayed as job method when called
104+
105+
This patch method has to be called in ``_register_hook`` (example
106+
below).
107+
108+
When a method is patched, any call to the method will not directly
109+
execute the method's body, but will instead enqueue a job.
110+
111+
When a ``context_key`` is set when calling ``_patch_job_auto_delay``,
112+
the patched method is automatically delayed only when this key is
113+
``True`` in the caller's context. It is advised to patch the method
114+
with a ``context_key``, because making the automatic delay *in any
115+
case* can produce nasty and unexpected side effects (e.g. another
116+
module calls the method and expects it to be computed before doing
117+
something else, expecting a result, ...).
118+
119+
A typical use case is when a method in a module we don't control is
120+
called synchronously in the middle of another method, and we'd like all
121+
the calls to this method become asynchronous.
122+
123+
The options of the job usually passed to ``with_delay()`` (priority,
124+
description, identity_key, ...) can be returned in a dictionary by a
125+
method named after the name of the method suffixed by ``_job_options``
126+
which takes the same parameters as the initial method.
127+
128+
It is still possible to force synchronous execution of the method by
129+
setting a key ``_job_force_sync`` to True in the environment context.
130+
131+
Example patching the "foo" method to be automatically delayed as job
132+
(the job options method is optional):
133+
134+
.. code-block:: python
135+
136+
# original method:
137+
def foo(self, arg1):
138+
print("hello", arg1)
139+
140+
def large_method(self):
141+
# doing a lot of things
142+
self.foo("world)
143+
# doing a lot of other things
144+
145+
def button_x(self):
146+
self.with_context(auto_delay_foo=True).large_method()
147+
148+
# auto delay patch:
149+
def foo_job_options(self, arg1):
150+
return {
151+
"priority": 100,
152+
"description": "Saying hello to {}".format(arg1)
153+
}
154+
155+
def _register_hook(self):
156+
self._patch_method(
157+
"foo",
158+
self._patch_job_auto_delay("foo", context_key="auto_delay_foo")
159+
)
160+
return super()._register_hook()
161+
162+
The result when ``button_x`` is called, is that a new job for ``foo``
163+
is delayed.
164+
"""
165+
166+
def auto_delay_wrapper(self, *args, **kwargs):
167+
# when no context_key is set, we delay in any case (warning, can be
168+
# dangerous)
169+
context_delay = self.env.context.get(context_key) if context_key else True
170+
if (
171+
self.env.context.get("job_uuid")
172+
or not context_delay
173+
or self.env.context.get("_job_force_sync")
174+
or self.env.context.get("test_queue_job_no_delay")
175+
):
176+
# we are in the job execution
177+
return auto_delay_wrapper.origin(self, *args, **kwargs)
178+
else:
179+
# replace the synchronous call by a job on itself
180+
method_name = auto_delay_wrapper.origin.__name__
181+
job_options_method = getattr(
182+
self, "{}_job_options".format(method_name), None
183+
)
184+
job_options = {}
185+
if job_options_method:
186+
job_options.update(job_options_method(*args, **kwargs))
187+
delayed = self.with_delay(**job_options)
188+
return getattr(delayed, method_name)(*args, **kwargs)
189+
190+
origin = getattr(self, method_name)
191+
return functools.update_wrapper(auto_delay_wrapper, origin)

0 commit comments

Comments
 (0)