|
1 | 1 | # Copyright 2016 Camptocamp |
2 | 2 | # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html) |
3 | 3 |
|
| 4 | +import functools |
4 | 5 | import logging |
5 | 6 | import os |
6 | 7 |
|
@@ -97,3 +98,94 @@ def with_delay( |
97 | 98 | channel=channel, |
98 | 99 | identity_key=identity_key, |
99 | 100 | ) |
| 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