mirror of
https://github.com/django/django.git
synced 2025-10-07 22:09:09 +00:00
This work implements what was defined in DEP 14 (https://github.com/django/deps/blob/main/accepted/0014-background-workers.rst). Thanks to Raphael Gaschignard, Eric Holscher, Ran Benita, Sarah Boyce, Jacob Walls, and Natalia Bidart for the reviews.
338 lines
12 KiB
Python
338 lines
12 KiB
Python
from typing import cast
|
|
from unittest import mock
|
|
|
|
from django.db import transaction
|
|
from django.db.utils import ConnectionHandler
|
|
from django.tasks import TaskResultStatus, default_task_backend, task_backends
|
|
from django.tasks.backends.dummy import DummyBackend
|
|
from django.tasks.base import Task
|
|
from django.tasks.exceptions import InvalidTask, TaskResultDoesNotExist
|
|
from django.test import (
|
|
SimpleTestCase,
|
|
TransactionTestCase,
|
|
override_settings,
|
|
skipIfDBFeature,
|
|
)
|
|
|
|
from . import tasks as test_tasks
|
|
|
|
|
|
@override_settings(
|
|
TASKS={
|
|
"default": {
|
|
"BACKEND": "django.tasks.backends.dummy.DummyBackend",
|
|
"QUEUES": [],
|
|
"ENQUEUE_ON_COMMIT": False,
|
|
}
|
|
}
|
|
)
|
|
class DummyBackendTestCase(SimpleTestCase):
|
|
def setUp(self):
|
|
default_task_backend.clear()
|
|
|
|
def test_using_correct_backend(self):
|
|
self.assertEqual(default_task_backend, task_backends["default"])
|
|
self.assertIsInstance(task_backends["default"], DummyBackend)
|
|
self.assertEqual(default_task_backend.alias, "default")
|
|
self.assertEqual(default_task_backend.options, {})
|
|
|
|
def test_enqueue_task(self):
|
|
for task in [test_tasks.noop_task, test_tasks.noop_task_async]:
|
|
with self.subTest(task):
|
|
result = cast(Task, task).enqueue(1, two=3)
|
|
|
|
self.assertEqual(result.status, TaskResultStatus.READY)
|
|
self.assertIs(result.is_finished, False)
|
|
self.assertIsNone(result.started_at)
|
|
self.assertIsNone(result.last_attempted_at)
|
|
self.assertIsNone(result.finished_at)
|
|
with self.assertRaisesMessage(ValueError, "Task has not finished yet"):
|
|
result.return_value
|
|
self.assertEqual(result.task, task)
|
|
self.assertEqual(result.args, [1])
|
|
self.assertEqual(result.kwargs, {"two": 3})
|
|
self.assertEqual(result.attempts, 0)
|
|
|
|
self.assertIn(result, default_task_backend.results)
|
|
|
|
async def test_enqueue_task_async(self):
|
|
for task in [test_tasks.noop_task, test_tasks.noop_task_async]:
|
|
with self.subTest(task):
|
|
result = await cast(Task, task).aenqueue()
|
|
|
|
self.assertEqual(result.status, TaskResultStatus.READY)
|
|
self.assertIs(result.is_finished, False)
|
|
self.assertIsNone(result.started_at)
|
|
self.assertIsNone(result.last_attempted_at)
|
|
self.assertIsNone(result.finished_at)
|
|
with self.assertRaisesMessage(ValueError, "Task has not finished yet"):
|
|
result.return_value
|
|
self.assertEqual(result.task, task)
|
|
self.assertEqual(result.args, [])
|
|
self.assertEqual(result.kwargs, {})
|
|
self.assertEqual(result.attempts, 0)
|
|
|
|
self.assertIn(result, default_task_backend.results)
|
|
|
|
def test_get_result(self):
|
|
result = default_task_backend.enqueue(test_tasks.noop_task, (), {})
|
|
|
|
new_result = default_task_backend.get_result(result.id)
|
|
|
|
self.assertEqual(result, new_result)
|
|
|
|
async def test_get_result_async(self):
|
|
result = await default_task_backend.aenqueue(test_tasks.noop_task, (), {})
|
|
|
|
new_result = await default_task_backend.aget_result(result.id)
|
|
|
|
self.assertEqual(result, new_result)
|
|
|
|
def test_refresh_result(self):
|
|
result = default_task_backend.enqueue(
|
|
test_tasks.calculate_meaning_of_life, (), {}
|
|
)
|
|
|
|
enqueued_result = default_task_backend.results[0]
|
|
object.__setattr__(enqueued_result, "status", TaskResultStatus.SUCCESSFUL)
|
|
|
|
self.assertEqual(result.status, TaskResultStatus.READY)
|
|
result.refresh()
|
|
self.assertEqual(result.status, TaskResultStatus.SUCCESSFUL)
|
|
|
|
async def test_refresh_result_async(self):
|
|
result = await default_task_backend.aenqueue(
|
|
test_tasks.calculate_meaning_of_life, (), {}
|
|
)
|
|
|
|
enqueued_result = default_task_backend.results[0]
|
|
object.__setattr__(enqueued_result, "status", TaskResultStatus.SUCCESSFUL)
|
|
|
|
self.assertEqual(result.status, TaskResultStatus.READY)
|
|
await result.arefresh()
|
|
self.assertEqual(result.status, TaskResultStatus.SUCCESSFUL)
|
|
|
|
async def test_get_missing_result(self):
|
|
with self.assertRaises(TaskResultDoesNotExist):
|
|
default_task_backend.get_result("123")
|
|
|
|
with self.assertRaises(TaskResultDoesNotExist):
|
|
await default_task_backend.aget_result("123")
|
|
|
|
def test_enqueue_on_commit(self):
|
|
self.assertIs(
|
|
default_task_backend._get_enqueue_on_commit_for_task(
|
|
test_tasks.enqueue_on_commit_task
|
|
),
|
|
True,
|
|
)
|
|
|
|
def test_enqueue_logs(self):
|
|
with self.assertLogs("django.tasks", level="DEBUG") as captured_logs:
|
|
result = test_tasks.noop_task.enqueue()
|
|
|
|
self.assertEqual(len(captured_logs.output), 1)
|
|
self.assertIn("enqueued", captured_logs.output[0])
|
|
self.assertIn(result.id, captured_logs.output[0])
|
|
|
|
def test_errors(self):
|
|
result = test_tasks.noop_task.enqueue()
|
|
self.assertEqual(result.errors, [])
|
|
|
|
def test_validate_disallowed_async_task(self):
|
|
with mock.patch.multiple(default_task_backend, supports_async_task=False):
|
|
with self.assertRaisesMessage(
|
|
InvalidTask, "Backend does not support async Tasks."
|
|
):
|
|
default_task_backend.validate_task(test_tasks.noop_task_async)
|
|
|
|
def test_check(self):
|
|
errors = list(default_task_backend.check())
|
|
self.assertEqual(len(errors), 0, errors)
|
|
|
|
@override_settings(
|
|
TASKS={
|
|
"default": {
|
|
"BACKEND": "django.tasks.backends.dummy.DummyBackend",
|
|
"ENQUEUE_ON_COMMIT": True,
|
|
}
|
|
}
|
|
)
|
|
@mock.patch("django.tasks.backends.base.connections", ConnectionHandler({}))
|
|
def test_enqueue_on_commit_with_no_databases(self):
|
|
self.assertIn(
|
|
"tasks.E001", {error.id for error in default_task_backend.check()}
|
|
)
|
|
|
|
def test_takes_context(self):
|
|
result = test_tasks.get_task_id.enqueue()
|
|
self.assertEqual(result.status, TaskResultStatus.READY)
|
|
|
|
def test_clear(self):
|
|
result = test_tasks.noop_task.enqueue()
|
|
|
|
default_task_backend.get_result(result.id)
|
|
|
|
default_task_backend.clear()
|
|
|
|
with self.assertRaisesMessage(TaskResultDoesNotExist, result.id):
|
|
default_task_backend.get_result(result.id)
|
|
|
|
def test_validate_on_enqueue(self):
|
|
task_with_custom_queue_name = test_tasks.noop_task.using(
|
|
queue_name="unknown_queue"
|
|
)
|
|
|
|
with override_settings(
|
|
TASKS={
|
|
"default": {
|
|
"BACKEND": "django.tasks.backends.dummy.DummyBackend",
|
|
"QUEUES": ["queue-1"],
|
|
"ENQUEUE_ON_COMMIT": False,
|
|
}
|
|
}
|
|
):
|
|
with self.assertRaisesMessage(
|
|
InvalidTask, "Queue 'unknown_queue' is not valid for backend"
|
|
):
|
|
task_with_custom_queue_name.enqueue()
|
|
|
|
async def test_validate_on_aenqueue(self):
|
|
task_with_custom_queue_name = test_tasks.noop_task.using(
|
|
queue_name="unknown_queue"
|
|
)
|
|
|
|
with override_settings(
|
|
TASKS={
|
|
"default": {
|
|
"BACKEND": "django.tasks.backends.dummy.DummyBackend",
|
|
"QUEUES": ["queue-1"],
|
|
"ENQUEUE_ON_COMMIT": False,
|
|
}
|
|
}
|
|
):
|
|
with self.assertRaisesMessage(
|
|
InvalidTask, "Queue 'unknown_queue' is not valid for backend"
|
|
):
|
|
await task_with_custom_queue_name.aenqueue()
|
|
|
|
|
|
class DummyBackendTransactionTestCase(TransactionTestCase):
|
|
available_apps = []
|
|
|
|
@override_settings(
|
|
TASKS={
|
|
"default": {
|
|
"BACKEND": "django.tasks.backends.dummy.DummyBackend",
|
|
"ENQUEUE_ON_COMMIT": True,
|
|
}
|
|
}
|
|
)
|
|
def test_wait_until_transaction_commit(self):
|
|
self.assertIs(default_task_backend.enqueue_on_commit, True)
|
|
self.assertIs(
|
|
default_task_backend._get_enqueue_on_commit_for_task(test_tasks.noop_task),
|
|
True,
|
|
)
|
|
|
|
with transaction.atomic():
|
|
test_tasks.noop_task.enqueue()
|
|
|
|
self.assertEqual(len(default_task_backend.results), 0)
|
|
|
|
self.assertEqual(len(default_task_backend.results), 1)
|
|
|
|
@override_settings(
|
|
TASKS={
|
|
"default": {
|
|
"BACKEND": "django.tasks.backends.dummy.DummyBackend",
|
|
"ENQUEUE_ON_COMMIT": False,
|
|
}
|
|
}
|
|
)
|
|
def test_doesnt_wait_until_transaction_commit(self):
|
|
self.assertIs(default_task_backend.enqueue_on_commit, False)
|
|
self.assertIs(
|
|
default_task_backend._get_enqueue_on_commit_for_task(test_tasks.noop_task),
|
|
False,
|
|
)
|
|
|
|
with transaction.atomic():
|
|
result = test_tasks.noop_task.enqueue()
|
|
|
|
self.assertIsNotNone(result.enqueued_at)
|
|
|
|
self.assertEqual(len(default_task_backend.results), 1)
|
|
|
|
self.assertEqual(len(default_task_backend.results), 1)
|
|
|
|
@override_settings(
|
|
TASKS={
|
|
"default": {
|
|
"BACKEND": "django.tasks.backends.dummy.DummyBackend",
|
|
}
|
|
}
|
|
)
|
|
def test_wait_until_transaction_by_default(self):
|
|
self.assertIs(default_task_backend.enqueue_on_commit, True)
|
|
self.assertIs(
|
|
default_task_backend._get_enqueue_on_commit_for_task(test_tasks.noop_task),
|
|
True,
|
|
)
|
|
|
|
with transaction.atomic():
|
|
result = test_tasks.noop_task.enqueue()
|
|
|
|
self.assertIsNone(result.enqueued_at)
|
|
|
|
self.assertEqual(len(default_task_backend.results), 0)
|
|
|
|
self.assertEqual(len(default_task_backend.results), 1)
|
|
self.assertIsNone(result.enqueued_at)
|
|
result.refresh()
|
|
self.assertIsNotNone(result.enqueued_at)
|
|
|
|
@override_settings(
|
|
TASKS={
|
|
"default": {
|
|
"BACKEND": "django.tasks.backends.dummy.DummyBackend",
|
|
"ENQUEUE_ON_COMMIT": False,
|
|
}
|
|
}
|
|
)
|
|
def test_task_specific_enqueue_on_commit(self):
|
|
self.assertIs(default_task_backend.enqueue_on_commit, False)
|
|
self.assertIs(test_tasks.enqueue_on_commit_task.enqueue_on_commit, True)
|
|
self.assertIs(
|
|
default_task_backend._get_enqueue_on_commit_for_task(
|
|
test_tasks.enqueue_on_commit_task
|
|
),
|
|
True,
|
|
)
|
|
|
|
with transaction.atomic():
|
|
result = test_tasks.enqueue_on_commit_task.enqueue()
|
|
|
|
self.assertIsNone(result.enqueued_at)
|
|
|
|
self.assertEqual(len(default_task_backend.results), 0)
|
|
|
|
self.assertEqual(len(default_task_backend.results), 1)
|
|
self.assertIsNone(result.enqueued_at)
|
|
result.refresh()
|
|
self.assertIsNotNone(result.enqueued_at)
|
|
|
|
@override_settings(
|
|
TASKS={
|
|
"default": {
|
|
"BACKEND": "django.tasks.backends.dummy.DummyBackend",
|
|
"ENQUEUE_ON_COMMIT": True,
|
|
}
|
|
}
|
|
)
|
|
@skipIfDBFeature("supports_transactions")
|
|
def test_enqueue_on_commit_with_no_transactions(self):
|
|
self.assertIn(
|
|
"tasks.E002", {error.id for error in default_task_backend.check()}
|
|
)
|