Skip to content

Commit 41385f7

Browse files
Define priority range and semantics (#70)
1 parent 8ce1fd8 commit 41385f7

File tree

7 files changed

+110
-13
lines changed

7 files changed

+110
-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
@@ -4,12 +4,20 @@
44

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

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

1523
logger = logging.getLogger("django_tasks.backends.database")
@@ -72,7 +80,7 @@ class DBTaskResult(GenericBase[P, T], models.Model):
7280

7381
args_kwargs = models.JSONField()
7482

75-
priority = models.PositiveSmallIntegerField(default=0)
83+
priority = models.IntegerField(default=DEFAULT_PRIORITY)
7684

7785
task_path = models.TextField()
7886

@@ -89,6 +97,12 @@ class Meta:
8997
ordering = [F("priority").desc(), F("run_after").desc(nulls_last=True)]
9098
verbose_name = "Task Result"
9199
verbose_name_plural = "Task Results"
100+
constraints = [
101+
CheckConstraint(
102+
check=Q(priority__range=(MIN_PRIORITY, MAX_PRIORITY)),
103+
name="priority_range",
104+
)
105+
]
92106

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

django_tasks/task.py

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

2929
DEFAULT_TASK_BACKEND_ALIAS = "default"
3030
DEFAULT_QUEUE_NAME = "default"
31+
MIN_PRIORITY = -100
32+
MAX_PRIORITY = 100
33+
DEFAULT_PRIORITY = 0
3134

3235

3336
class ResultStatus(TextChoices):
@@ -70,6 +73,7 @@ def name(self) -> str:
7073

7174
def using(
7275
self,
76+
*,
7377
priority: Optional[int] = None,
7478
queue_name: Optional[str] = None,
7579
run_after: Optional[Union[datetime, timedelta]] = None,
@@ -163,7 +167,7 @@ def task(function: Callable[P, T], /) -> Task[P, T]: ...
163167
@overload
164168
def task(
165169
*,
166-
priority: int = 0,
170+
priority: int = DEFAULT_PRIORITY,
167171
queue_name: str = DEFAULT_QUEUE_NAME,
168172
backend: str = DEFAULT_TASK_BACKEND_ALIAS,
169173
) -> Callable[[Callable[P, T]], Task[P, T]]: ...
@@ -173,7 +177,7 @@ def task(
173177
def task(
174178
function: Optional[Callable[P, T]] = None,
175179
*,
176-
priority: int = 0,
180+
priority: int = DEFAULT_PRIORITY,
177181
queue_name: str = DEFAULT_QUEUE_NAME,
178182
backend: str = DEFAULT_TASK_BACKEND_ALIAS,
179183
) -> Union[Task[P, T], Callable[[Callable[P, T]], Task[P, T]]]:

tests/tests/test_database_backend.py

Lines changed: 34 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)
@@ -204,6 +205,34 @@ def test_database_backend_app_missing(self) -> None:
204205
self.assertEqual(len(errors), 1)
205206
self.assertIn("django_tasks.backends.database", errors[0].hint)
206207

208+
def test_priority_range_check(self) -> None:
209+
with self.assertRaises(IntegrityError):
210+
DBTaskResult.objects.create(
211+
task_path="", backend_name="default", priority=-101, args_kwargs={}
212+
)
213+
214+
with self.assertRaises(IntegrityError):
215+
DBTaskResult.objects.create(
216+
task_path="", backend_name="default", priority=101, args_kwargs={}
217+
)
218+
219+
# Django accepts the float, but only stores an int
220+
result = DBTaskResult.objects.create(
221+
task_path="", backend_name="default", priority=3.1, args_kwargs={}
222+
)
223+
result.refresh_from_db()
224+
self.assertEqual(result.priority, 3)
225+
226+
DBTaskResult.objects.create(
227+
task_path="", backend_name="default", priority=100, args_kwargs={}
228+
)
229+
DBTaskResult.objects.create(
230+
task_path="", backend_name="default", priority=-100, args_kwargs={}
231+
)
232+
DBTaskResult.objects.create(
233+
task_path="", backend_name="default", priority=0, args_kwargs={}
234+
)
235+
207236

208237
@override_settings(
209238
TASKS={
@@ -426,6 +455,7 @@ def test_run_after_priority(self) -> None:
426455
high_priority_result = test_tasks.noop_task.using(priority=10).enqueue()
427456

428457
low_priority_result = test_tasks.noop_task.using(priority=2).enqueue()
458+
lower_priority_result = test_tasks.noop_task.using(priority=-2).enqueue()
429459

430460
self.assertEqual(
431461
[dbt.task_result for dbt in DBTaskResult.objects.all()],
@@ -435,6 +465,7 @@ def test_run_after_priority(self) -> None:
435465
low_priority_result,
436466
far_future_result,
437467
future_result,
468+
lower_priority_result,
438469
],
439470
)
440471

@@ -443,6 +474,7 @@ def test_run_after_priority(self) -> None:
443474
[
444475
high_priority_result,
445476
low_priority_result,
477+
lower_priority_result,
446478
],
447479
)
448480

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)