Skip to content

Commit cdbb61d

Browse files
committed
Define priority range and semantics
1 parent fe94300 commit cdbb61d

File tree

7 files changed

+108
-13
lines changed

7 files changed

+108
-13
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def calculate_meaning_of_life() -> int:
5757

5858
The task decorator accepts a few arguments to customize the task:
5959

60-
- `priority`: The priority of the task (larger numbers are higher priority)
60+
- `priority`: The priority of the task (between -100 and 100. Larger numbers are higher priority. 0 by default)
6161
- `queue_name`: Whether to run the task on a specific queue
6262
- `backend`: Name of the backend for this task to use (as defined in `TASKS`)
6363

django_tasks/backends/base.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from typing_extensions import ParamSpec
99

1010
from django_tasks.exceptions import InvalidTaskError
11-
from django_tasks.task import Task, TaskResult
11+
from django_tasks.task import MAX_PRIORITY, MIN_PRIORITY, Task, TaskResult
1212
from django_tasks.utils import is_global_function
1313

1414
T = TypeVar("T")
@@ -45,8 +45,14 @@ def validate_task(self, task: Task) -> None:
4545
if not self.supports_async_task and iscoroutinefunction(task.func):
4646
raise InvalidTaskError("Backend does not support async tasks")
4747

48-
if task.priority < 0:
49-
raise InvalidTaskError("priority must be zero or greater")
48+
if (
49+
task.priority < MIN_PRIORITY
50+
or task.priority > MAX_PRIORITY
51+
or int(task.priority) != task.priority
52+
):
53+
raise InvalidTaskError(
54+
f"priority must be a whole number between {MIN_PRIORITY} and {MAX_PRIORITY}"
55+
)
5056

5157
if not self.supports_defer and task.run_after is not None:
5258
raise InvalidTaskError("Backend does not support run_after")
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 4.2.13 on 2024-07-10 15:48
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
("django_tasks_database", "0004_dbtaskresult_started_at"),
9+
]
10+
11+
operations = [
12+
migrations.AlterField(
13+
model_name="dbtaskresult",
14+
name="priority",
15+
field=models.IntegerField(default=0),
16+
),
17+
migrations.AddConstraint(
18+
model_name="dbtaskresult",
19+
constraint=models.CheckConstraint(
20+
check=models.Q(("priority__range", (-100, 100))), name="priority_range"
21+
),
22+
),
23+
]

django_tasks/backends/database/models.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,20 @@
33

44
from django.core.exceptions import SuspiciousOperation
55
from django.db import models
6-
from django.db.models import F
6+
from django.db.models import F, Q
7+
from django.db.models.constraints import CheckConstraint
78
from django.utils import timezone
89
from django.utils.module_loading import import_string
910
from typing_extensions import ParamSpec
1011

11-
from django_tasks.task import DEFAULT_QUEUE_NAME, ResultStatus, Task
12+
from django_tasks.task import (
13+
DEFAULT_PRIORITY,
14+
DEFAULT_QUEUE_NAME,
15+
MAX_PRIORITY,
16+
MIN_PRIORITY,
17+
ResultStatus,
18+
Task,
19+
)
1220
from django_tasks.utils import retry
1321

1422
T = TypeVar("T")
@@ -69,7 +77,7 @@ class DBTaskResult(GenericBase[P, T], models.Model):
6977

7078
args_kwargs = models.JSONField()
7179

72-
priority = models.PositiveSmallIntegerField(default=0)
80+
priority = models.IntegerField(default=DEFAULT_PRIORITY)
7381

7482
task_path = models.TextField()
7583

@@ -86,6 +94,12 @@ class Meta:
8694
ordering = [F("priority").desc(), F("run_after").desc(nulls_last=True)]
8795
verbose_name = "Task Result"
8896
verbose_name_plural = "Task Results"
97+
constraints = [
98+
CheckConstraint(
99+
check=Q(priority__range=(MIN_PRIORITY, MAX_PRIORITY)),
100+
name="priority_range",
101+
)
102+
]
89103

90104
@property
91105
def task(self) -> Task[P, T]:

django_tasks/task.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626

2727
DEFAULT_TASK_BACKEND_ALIAS = "default"
2828
DEFAULT_QUEUE_NAME = "default"
29+
MIN_PRIORITY = -100
30+
MAX_PRIORITY = 100
31+
DEFAULT_PRIORITY = 0
2932

3033

3134
class ResultStatus(TextChoices):
@@ -68,6 +71,7 @@ def name(self) -> str:
6871

6972
def using(
7073
self,
74+
*,
7175
priority: Optional[int] = None,
7276
queue_name: Optional[str] = None,
7377
run_after: Optional[Union[datetime, timedelta]] = None,
@@ -161,7 +165,7 @@ def task(function: Callable[P, T], /) -> Task[P, T]: ...
161165
@overload
162166
def task(
163167
*,
164-
priority: int = 0,
168+
priority: int = DEFAULT_PRIORITY,
165169
queue_name: str = DEFAULT_QUEUE_NAME,
166170
backend: str = DEFAULT_TASK_BACKEND_ALIAS,
167171
) -> Callable[[Callable[P, T]], Task[P, T]]: ...
@@ -171,7 +175,7 @@ def task(
171175
def task(
172176
function: Optional[Callable[P, T]] = None,
173177
*,
174-
priority: int = 0,
178+
priority: int = DEFAULT_PRIORITY,
175179
queue_name: str = DEFAULT_QUEUE_NAME,
176180
backend: str = DEFAULT_TASK_BACKEND_ALIAS,
177181
) -> Union[Task[P, T], Callable[[Callable[P, T]], Task[P, T]]]:

tests/tests/test_database_backend.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77

88
from django.core.exceptions import SuspiciousOperation
99
from django.core.management import call_command, execute_from_command_line
10-
from django.test import TestCase, TransactionTestCase, override_settings
10+
from django.db.utils import IntegrityError
11+
from django.test import TransactionTestCase, override_settings
1112
from django.urls import reverse
1213
from django.utils import timezone
1314

@@ -29,7 +30,7 @@
2930
}
3031
}
3132
)
32-
class DatabaseBackendTestCase(TestCase):
33+
class DatabaseBackendTestCase(TransactionTestCase):
3334
def test_using_correct_backend(self) -> None:
3435
self.assertEqual(default_task_backend, tasks["default"])
3536
self.assertIsInstance(tasks["default"], DatabaseBackend)
@@ -202,6 +203,32 @@ def test_database_backend_app_missing(self) -> None:
202203
self.assertEqual(len(errors), 1)
203204
self.assertIn("django_tasks.backends.database", errors[0].hint)
204205

206+
def test_priority_range_check(self) -> None:
207+
with self.assertRaises(IntegrityError):
208+
DBTaskResult.objects.create(
209+
task_path="", backend_name="default", priority=-101, args_kwargs={}
210+
)
211+
212+
with self.assertRaises(IntegrityError):
213+
DBTaskResult.objects.create(
214+
task_path="", backend_name="default", priority=101, args_kwargs={}
215+
)
216+
217+
with self.assertRaises(IntegrityError):
218+
DBTaskResult.objects.create(
219+
task_path="", backend_name="default", priority=3.1, args_kwargs={}
220+
)
221+
222+
DBTaskResult.objects.create(
223+
task_path="", backend_name="default", priority=100, args_kwargs={}
224+
)
225+
DBTaskResult.objects.create(
226+
task_path="", backend_name="default", priority=-100, args_kwargs={}
227+
)
228+
DBTaskResult.objects.create(
229+
task_path="", backend_name="default", priority=0, args_kwargs={}
230+
)
231+
205232

206233
@override_settings(
207234
TASKS={
@@ -402,6 +429,7 @@ def test_run_after_priority(self) -> None:
402429
high_priority_result = test_tasks.noop_task.using(priority=10).enqueue()
403430

404431
low_priority_result = test_tasks.noop_task.using(priority=2).enqueue()
432+
lower_priority_result = test_tasks.noop_task.using(priority=-2).enqueue()
405433

406434
self.assertEqual(
407435
[dbt.task_result for dbt in DBTaskResult.objects.all()],
@@ -411,6 +439,7 @@ def test_run_after_priority(self) -> None:
411439
low_priority_result,
412440
far_future_result,
413441
future_result,
442+
lower_priority_result,
414443
],
415444
)
416445

@@ -419,6 +448,7 @@ def test_run_after_priority(self) -> None:
419448
[
420449
high_priority_result,
421450
low_priority_result,
451+
lower_priority_result,
422452
],
423453
)
424454

tests/tests/test_tasks.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
InvalidTaskError,
2121
ResultDoesNotExist,
2222
)
23+
from django_tasks.task import MAX_PRIORITY, MIN_PRIORITY
2324
from tests import tasks as test_tasks
2425

2526

@@ -133,9 +134,26 @@ def test_naive_datetime(self) -> None:
133134

134135
def test_invalid_priority(self) -> None:
135136
with self.assertRaisesMessage(
136-
InvalidTaskError, "priority must be zero or greater"
137+
InvalidTaskError,
138+
f"priority must be a whole number between {MIN_PRIORITY} and {MAX_PRIORITY}",
137139
):
138-
test_tasks.noop_task.using(priority=-1)
140+
test_tasks.noop_task.using(priority=-101)
141+
142+
with self.assertRaisesMessage(
143+
InvalidTaskError,
144+
f"priority must be a whole number between {MIN_PRIORITY} and {MAX_PRIORITY}",
145+
):
146+
test_tasks.noop_task.using(priority=101)
147+
148+
with self.assertRaisesMessage(
149+
InvalidTaskError,
150+
f"priority must be a whole number between {MIN_PRIORITY} and {MAX_PRIORITY}",
151+
):
152+
test_tasks.noop_task.using(priority=3.1) # type:ignore[arg-type]
153+
154+
test_tasks.noop_task.using(priority=100)
155+
test_tasks.noop_task.using(priority=-100)
156+
test_tasks.noop_task.using(priority=0)
139157

140158
def test_call_task(self) -> None:
141159
self.assertEqual(test_tasks.calculate_meaning_of_life.call(), 42)

0 commit comments

Comments
 (0)