diff --git a/queue_job/delay.py b/queue_job/delay.py index 0ba54e48a9..516a10aef6 100644 --- a/queue_job/delay.py +++ b/queue_job/delay.py @@ -443,6 +443,7 @@ class Delayable: "recordset", "_graph", "_job_method", + "_job_method_name", "_job_args", "_job_kwargs", "_generated_job", @@ -471,6 +472,7 @@ def __init__( self.identity_key = identity_key self._job_method = None + self._job_method_name = "" self._job_args = () self._job_kwargs = {} @@ -484,8 +486,7 @@ def _tail(self): def __repr__(self): return ( - f"Delayable({self.recordset}." - f"{self._job_method.__name__ if self._job_method else ''}" + f"Delayable({self.recordset}.{self._job_method_name}" f"({self._job_args}, {self._job_kwargs}))" ) @@ -548,7 +549,8 @@ def split(self, size, chain=False): identity_key=self.identity_key, ) # Update the __self__ - delayable._job_method = getattr(recordset, self._job_method.__name__) + delayable._job_method = getattr(recordset, self._job_method_name) + delayable._job_method_name = self._job_method_name delayable._job_args = self._job_args delayable._job_kwargs = self._job_kwargs @@ -557,7 +559,7 @@ def split(self, size, chain=False): description = self.description or ( self._job_method.__doc__.splitlines()[0].strip() if self._job_method.__doc__ - else f"{self.recordset._name}.{self._job_method.__name__}" + else f"{self.recordset._name}.{self._job_method_name}" ) for index, delayable in enumerate(delayables): delayable.set( @@ -574,6 +576,7 @@ def _build_job(self): return self._generated_job self._generated_job = Job( self._job_method, + method_name=self._job_method_name, args=self._job_args, kwargs=self._job_kwargs, priority=self.priority, @@ -599,6 +602,10 @@ def __getattr__(self, name): ) recordset_method = getattr(self.recordset, name) self._job_method = recordset_method + # Keep the name that was looked up: the method found under it may + # carry another __name__ (e.g. replaced by auditlog), and the job must + # resolve the same attribute again when it is loaded and run. + self._job_method_name = name return self._store_args def _execute_direct(self): diff --git a/queue_job/job.py b/queue_job/job.py index a69a71ddc5..736484a81a 100644 --- a/queue_job/job.py +++ b/queue_job/job.py @@ -310,6 +310,7 @@ def _load_from_db_record(cls, job_db_record): job_ = cls( method, + method_name=method_name, args=args, kwargs=kwargs, priority=stored.priority, @@ -385,6 +386,7 @@ def __init__( description=None, channel=None, identity_key=None, + method_name=None, ): """Create a Job @@ -409,6 +411,9 @@ def __init__( :param identity_key: A hash to uniquely identify a job, or a function that returns this hash (the function takes the job as argument) + :param method_name: name of the method on the recordset. Defaults to + ``func.__name__``; pass it when the method was + looked up under a different name than its own. """ if args is None: args = () @@ -424,7 +429,7 @@ def __init__( raise TypeError("Job accepts only methods of Models") recordset = func.__self__ - self.method_name = func.__name__ + self.method_name = method_name or func.__name__ self.recordset = recordset self.job_config = ( @@ -781,7 +786,7 @@ def description(self): elif self.func.__doc__: return self.func.__doc__.splitlines()[0].strip() else: - return f"{self.model_name}.{self.func.__name__}" + return f"{self.model_name}.{self.method_name}" @property def uuid(self): diff --git a/test_queue_job/tests/test_delayable.py b/test_queue_job/tests/test_delayable.py index 3bd6cfd5cd..7806c7c434 100644 --- a/test_queue_job/tests/test_delayable.py +++ b/test_queue_job/tests/test_delayable.py @@ -14,6 +14,7 @@ chain, group, ) +from odoo.addons.queue_job.job import Job class TestDelayable(common.TransactionCase): @@ -308,3 +309,38 @@ def test_delay_job_already_exists(self): node.on_done(node2).delay() self.assert_generated_job(node, node2) self.assert_dependencies({node: {}, node2: {node}}) + + def _replace_testing_method_under_another_name(self): + """Put a function named ``testing_method_renamed`` into the + ``testing_method`` slot, the way auditlog replaces write and unlink.""" + cls = type(self.test_model) + original = cls.testing_method + + def testing_method_renamed(self, *args, **kwargs): + return original(self, *args, **kwargs) + + self.patch(cls, "testing_method", testing_method_renamed) + + def test_delayable_keeps_requested_method_name(self): + self._replace_testing_method_under_another_name() + node = Delayable(self.test_model).testing_method(1) + node.delay() + job = node._generated_job + self.assertEqual(job.method_name, "testing_method") + self.assertEqual(job.db_record().method_name, "testing_method") + # Loading the job resolves the same name again + loaded = Job.load(self.env, job.uuid) + self.assertEqual(loaded.method_name, "testing_method") + + def test_with_delay_keeps_requested_method_name(self): + self._replace_testing_method_under_another_name() + job = self.test_model.with_delay().testing_method(1) + self.assertEqual(job.method_name, "testing_method") + + def test_delayable_split_keeps_requested_method_name(self): + self._replace_testing_method_under_another_name() + records = self.test_model.create([{"name": "a"}, {"name": "b"}]) + group = Delayable(records).testing_method(1).split(1) + group.delay() + for node in group._delayables: + self.assertEqual(node._generated_job.method_name, "testing_method")