Skip to content

Commit 8c8b833

Browse files
varunkasyapjacobtylerwalls
authored andcommitted
Fixed #36919 -- Allowed Task and TaskResult to be pickled.
1 parent 02a7d43 commit 8c8b833

3 files changed

Lines changed: 47 additions & 1 deletion

File tree

django/tasks/base.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from collections.abc import Callable
2-
from dataclasses import dataclass, field, replace
2+
from dataclasses import dataclass, field, fields, replace
33
from datetime import datetime
44
from inspect import isclass, iscoroutinefunction
55
from typing import Any
@@ -55,6 +55,23 @@ class Task:
5555
def __post_init__(self):
5656
self.get_backend().validate_task(self)
5757

58+
@classmethod
59+
def _reconstruct(cls, kwargs):
60+
func_path = kwargs["func"]
61+
try:
62+
func = import_string(func_path)
63+
kwargs["func"] = func.func
64+
except (ImportError, AttributeError) as e:
65+
msg = f"Expected {func_path!r} to point to a Task instance."
66+
raise ValueError(msg) from e
67+
return cls(**kwargs)
68+
69+
def __reduce__(self):
70+
kwargs = {f.name: getattr(self, f.name) for f in fields(self)}
71+
kwargs["func"] = self.module_path
72+
73+
return (self.__class__._reconstruct, (kwargs,))
74+
5875
@property
5976
def name(self):
6077
return self.func.__name__

docs/releases/6.1.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,9 @@ Tasks
378378
forwarded to the backend's
379379
:attr:`~django.tasks.backends.base.BaseTaskBackend.task_class`.
380380

381+
* :class:`~django.tasks.Task` and :class:`~django.tasks.TaskResult` instances
382+
can now be pickled and unpickled.
383+
381384
Templates
382385
~~~~~~~~~
383386

tests/tasks/test_tasks.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import dataclasses
2+
import pickle
23
from datetime import datetime
34

45
from django.tasks import (
@@ -269,6 +270,31 @@ def test_module_path(self):
269270
test_tasks.noop_task_async,
270271
)
271272

273+
def test_pickle_task(self):
274+
pickled_task = pickle.dumps(test_tasks.noop_task)
275+
unpickled_task = pickle.loads(pickled_task)
276+
277+
self.assertEqual(unpickled_task, test_tasks.noop_task)
278+
279+
def test_unpickle_arbitrary_string(self):
280+
kwargs = {"func": "does.not.exist.fake_task"}
281+
msg = "Expected 'does.not.exist.fake_task' to point to a Task instance."
282+
with self.assertRaisesMessage(ValueError, msg):
283+
Task._reconstruct(kwargs)
284+
285+
def test_unpickle_non_task_object(self):
286+
kwargs = {"func": "builtins.any"}
287+
msg = "Expected 'builtins.any' to point to a Task instance."
288+
with self.assertRaisesMessage(ValueError, msg):
289+
Task._reconstruct(kwargs)
290+
291+
def test_pickle_task_result(self):
292+
result = test_tasks.noop_task.enqueue()
293+
pickled_result = pickle.dumps(result)
294+
unpickled_result = pickle.loads(pickled_result)
295+
296+
self.assertEqual(unpickled_result, result)
297+
272298
@override_settings(TASKS={})
273299
def test_no_backends(self):
274300
with self.assertRaises(InvalidTaskBackend):

0 commit comments

Comments
 (0)