dj-queue 0.9.0__tar.gz → 0.9.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. {dj_queue-0.9.0 → dj_queue-0.9.1}/PKG-INFO +1 -1
  2. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/admin.py +2 -12
  3. dj_queue-0.9.1/dj_queue/contrib/prometheus.py +26 -0
  4. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/dashboard.py +15 -59
  5. dj_queue-0.9.1/dj_queue/metrics.py +162 -0
  6. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/observability.py +1 -16
  7. dj_queue-0.9.1/dj_queue/operations/_helpers.py +190 -0
  8. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/operations/concurrency.py +11 -9
  9. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/operations/jobs.py +118 -31
  10. dj_queue-0.9.1/dj_queue/queue_state.py +118 -0
  11. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/base.py +34 -14
  12. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/supervisor.py +52 -182
  13. dj_queue-0.9.1/dj_queue/runtime/topology.py +51 -0
  14. {dj_queue-0.9.0 → dj_queue-0.9.1}/pyproject.toml +1 -1
  15. dj_queue-0.9.0/dj_queue/contrib/prometheus.py +0 -128
  16. dj_queue-0.9.0/dj_queue/operations/_helpers.py +0 -41
  17. {dj_queue-0.9.0 → dj_queue-0.9.1}/LICENSE +0 -0
  18. {dj_queue-0.9.0 → dj_queue-0.9.1}/README.md +0 -0
  19. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/__init__.py +0 -0
  20. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/api.py +0 -0
  21. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/apps.py +0 -0
  22. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/backend.py +0 -0
  23. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/config.py +0 -0
  24. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/contrib/__init__.py +0 -0
  25. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/contrib/asgi.py +0 -0
  26. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/contrib/gunicorn.py +0 -0
  27. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/cron.py +0 -0
  28. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/db.py +0 -0
  29. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/exceptions.py +0 -0
  30. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/hooks.py +0 -0
  31. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/log.py +0 -0
  32. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/management/__init__.py +0 -0
  33. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/management/commands/__init__.py +0 -0
  34. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/management/commands/dj_queue.py +0 -0
  35. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/management/commands/dj_queue_health.py +0 -0
  36. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/management/commands/dj_queue_prune.py +0 -0
  37. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/migrations/0001_initial.py +0 -0
  38. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/migrations/0002_pause_semaphore.py +0 -0
  39. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/migrations/0003_recurringtask_recurringexecution.py +0 -0
  40. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/migrations/0004_dashboard.py +0 -0
  41. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/migrations/0005_remove_recurringexecution_dj_queue_recurring_executions_task_key_run_at_unique_and_more.py +0 -0
  42. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/migrations/0006_blockedexecution_dj_queue_bl_concurr_2d8393_idx_and_more.py +0 -0
  43. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/migrations/0007_recurringtask_next_run_at.py +0 -0
  44. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/migrations/0008_remove_blockedexecution_dj_queue_bl_concurr_1ce730_idx_and_more.py +0 -0
  45. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/migrations/__init__.py +0 -0
  46. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/models/__init__.py +0 -0
  47. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/models/jobs.py +0 -0
  48. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/models/recurring.py +0 -0
  49. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/models/runtime.py +0 -0
  50. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/operations/__init__.py +0 -0
  51. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/operations/_insert.py +0 -0
  52. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/operations/cleanup.py +0 -0
  53. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/operations/queues.py +0 -0
  54. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/operations/recurring.py +0 -0
  55. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/routers.py +0 -0
  56. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/__init__.py +0 -0
  57. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/connection_budget.py +0 -0
  58. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/dispatcher.py +0 -0
  59. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/errors.py +0 -0
  60. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/interruptible.py +0 -0
  61. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/notify.py +0 -0
  62. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/pidfile.py +0 -0
  63. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/pool.py +0 -0
  64. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/procline.py +0 -0
  65. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/scheduler.py +0 -0
  66. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/worker.py +0 -0
  67. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templates/admin/dj_queue/_dashboard_process_rows.html +0 -0
  68. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templates/admin/dj_queue/_dashboard_recurring_rows.html +0 -0
  69. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templates/admin/dj_queue/_dashboard_section_table.html +0 -0
  70. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templates/admin/dj_queue/_dashboard_semaphore_rows.html +0 -0
  71. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templates/admin/dj_queue/_paginator.html +0 -0
  72. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templates/admin/dj_queue/_queue_controls.html +0 -0
  73. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templates/admin/dj_queue/_sortable_header_cells.html +0 -0
  74. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templates/admin/dj_queue/change_form.html +0 -0
  75. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templates/admin/dj_queue/change_list.html +0 -0
  76. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templates/admin/dj_queue/dashboard.html +0 -0
  77. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templates/admin/dj_queue/includes/fieldset.html +0 -0
  78. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templates/admin/dj_queue/queue_jobs.html +0 -0
  79. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templatetags/__init__.py +0 -0
  80. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templatetags/dj_queue_admin.py +0 -0
  81. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/urls.py +0 -0
  82. {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dj-queue
3
- Version: 0.9.0
3
+ Version: 0.9.1
4
4
  Summary: Database-backed task queue backend for Django’s Tasks framework.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -35,6 +35,7 @@ from dj_queue.operations.jobs import (
35
35
  retry_failed_job,
36
36
  retry_failed_jobs,
37
37
  )
38
+ from dj_queue.queue_state import status_rank_expression
38
39
 
39
40
 
40
41
  class DjQueueFirstAdminSite(admin.AdminSite):
@@ -503,18 +504,7 @@ class JobAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
503
504
 
504
505
  def get_queryset(self, request):
505
506
  queryset = super().get_queryset(request)
506
- return queryset.annotate(
507
- status_rank=Case(
508
- When(ready_execution__isnull=False, then=Value(0)),
509
- When(scheduled_execution__isnull=False, then=Value(1)),
510
- When(claimed_execution__isnull=False, then=Value(2)),
511
- When(blocked_execution__isnull=False, then=Value(3)),
512
- When(failed_execution__isnull=False, then=Value(4)),
513
- When(finished_at__isnull=False, then=Value(5)),
514
- default=Value(99),
515
- output_field=IntegerField(),
516
- )
517
- )
507
+ return queryset.annotate(status_rank=status_rank_expression())
518
508
 
519
509
  @admin.display(description="status", ordering="status_rank")
520
510
  def display_status(self, obj):
@@ -0,0 +1,26 @@
1
+ try:
2
+ from prometheus_client import CollectorRegistry, generate_latest
3
+ from prometheus_client.core import GaugeMetricFamily
4
+ except ImportError:
5
+ DjQueueCollector = None
6
+ registry = None
7
+ generate_latest = None
8
+ else:
9
+ from dj_queue.metrics import metric_families
10
+
11
+ class DjQueueCollector:
12
+ """Prometheus collector that exposes dj_queue metrics from the shared observability snapshot."""
13
+
14
+ def collect(self):
15
+ for family in metric_families():
16
+ gauge = GaugeMetricFamily(
17
+ family.name,
18
+ family.help_text,
19
+ labels=list(family.labels),
20
+ )
21
+ for sample in family.samples:
22
+ gauge.add_metric(list(sample.labels), sample.value)
23
+ yield gauge
24
+
25
+ registry = CollectorRegistry(auto_describe=False)
26
+ registry.register(DjQueueCollector())
@@ -22,51 +22,20 @@ from dj_queue.operations.jobs import (
22
22
  enqueue_job_again,
23
23
  retry_failed_jobs,
24
24
  )
25
-
26
- QUEUE_STATE_CONFIG = {
27
- "ready": {
28
- "label": "ready",
29
- "job_actions": ({"name": "discard", "label": "discard selected"},),
30
- "query_filter": {"ready_execution__isnull": False},
31
- "query_order": ("-priority", "ready_execution__id"),
32
- },
33
- "claimed": {
34
- "label": "claimed",
35
- "job_actions": (),
36
- "query_filter": {"claimed_execution__isnull": False},
37
- "query_order": ("claimed_execution__created_at", "id"),
38
- },
39
- "scheduled": {
40
- "label": "scheduled",
41
- "job_actions": ({"name": "discard", "label": "discard selected"},),
42
- "query_filter": {"scheduled_execution__isnull": False},
43
- "query_order": ("scheduled_execution__scheduled_at", "-priority", "scheduled_execution__id"),
44
- },
45
- "blocked": {
46
- "label": "blocked",
47
- "job_actions": ({"name": "discard", "label": "discard selected"},),
48
- "query_filter": {"blocked_execution__isnull": False},
49
- "query_order": ("blocked_execution__expires_at", "-priority", "blocked_execution__id"),
50
- },
51
- "failed": {
52
- "label": "failed",
53
- "job_actions": (
54
- {"name": "retry", "label": "retry selected"},
55
- {"name": "discard", "label": "discard selected"},
56
- ),
57
- "query_filter": {"failed_execution__isnull": False},
58
- "query_order": ("-failed_execution__created_at", "id"),
59
- },
60
- "finished": {
61
- "label": "finished",
62
- "job_actions": ({"name": "enqueue", "label": "enqueue selected again"},),
63
- "query_filter": {"finished_at__isnull": False},
64
- "query_order": ("-finished_at", "id"),
65
- },
25
+ from dj_queue.queue_state import QUEUE_STATE_LABELS, QUEUE_STATES, queue_state_queryset
26
+
27
+
28
+ QUEUE_JOB_ACTIONS = {
29
+ "ready": ({"name": "discard", "label": "discard selected"},),
30
+ "claimed": (),
31
+ "scheduled": ({"name": "discard", "label": "discard selected"},),
32
+ "blocked": ({"name": "discard", "label": "discard selected"},),
33
+ "failed": (
34
+ {"name": "retry", "label": "retry selected"},
35
+ {"name": "discard", "label": "discard selected"},
36
+ ),
37
+ "finished": ({"name": "enqueue", "label": "enqueue selected again"},),
66
38
  }
67
-
68
- QUEUE_STATES = tuple((state, config["label"]) for state, config in QUEUE_STATE_CONFIG.items())
69
- QUEUE_STATE_LABELS = {state: config["label"] for state, config in QUEUE_STATE_CONFIG.items()}
70
39
  PAGE_SIZE = 100
71
40
  OVERVIEW_PAGE_SIZES = {
72
41
  "queues": 18,
@@ -555,7 +524,7 @@ def apply_job_action(*, backend_alias, queue_name, state, action, job_ids):
555
524
 
556
525
 
557
526
  def job_actions_for_state(state):
558
- return QUEUE_STATE_CONFIG[state]["job_actions"]
527
+ return QUEUE_JOB_ACTIONS[state]
559
528
 
560
529
 
561
530
  def _summary_cards(*, backend_alias, queue_rows, process_rows, recurring_rows, semaphore_rows):
@@ -1181,20 +1150,7 @@ def _failed_execution_changelist_url(backend_alias, **filters):
1181
1150
 
1182
1151
 
1183
1152
  def _jobs_for_queue_state(*, backend_alias, queue_name, state):
1184
- alias = get_database_alias(backend_alias)
1185
- queryset = (
1186
- Job.objects.using(alias)
1187
- .filter(backend_alias=backend_alias, queue_name=queue_name)
1188
- .select_related(
1189
- "ready_execution",
1190
- "scheduled_execution",
1191
- "claimed_execution__process",
1192
- "blocked_execution",
1193
- "failed_execution",
1194
- )
1195
- )
1196
- state_config = QUEUE_STATE_CONFIG[state]
1197
- return queryset.filter(**state_config["query_filter"]).order_by(*state_config["query_order"])
1153
+ return queue_state_queryset(backend_alias=backend_alias, queue_name=queue_name, state=state)
1198
1154
 
1199
1155
 
1200
1156
  def _next_run_at(schedule, now):
@@ -0,0 +1,162 @@
1
+ from dataclasses import dataclass
2
+
3
+ from dj_queue import observability
4
+
5
+
6
+ @dataclass(frozen=True, slots=True)
7
+ class MetricSample:
8
+ labels: tuple[str, ...]
9
+ value: float
10
+
11
+
12
+ @dataclass(frozen=True, slots=True)
13
+ class MetricFamily:
14
+ name: str
15
+ help_text: str
16
+ labels: tuple[str, ...]
17
+ samples: tuple[MetricSample, ...]
18
+
19
+
20
+ def metric_families(*, snapshots=None):
21
+ if snapshots is None:
22
+ snapshots = observability.all_backend_snapshots()
23
+
24
+ queue_jobs = []
25
+ queue_paused = []
26
+ queue_latency = []
27
+ queue_workers = []
28
+ runner_processes = []
29
+ runner_processes_by_kind = []
30
+ recurring_tasks = []
31
+ semaphores = []
32
+ process_rows = []
33
+ seen_queue_databases = set()
34
+
35
+ for snapshot in snapshots:
36
+ backend_alias = snapshot["backend_alias"]
37
+ queue_database_alias = snapshot["queue_database_alias"]
38
+ runner_metrics = snapshot["runner_metrics"]
39
+
40
+ for queue in snapshot["queue_rows"]:
41
+ for state in ("ready", "claimed", "scheduled", "blocked", "failed", "finished"):
42
+ queue_jobs.append(
43
+ MetricSample(
44
+ labels=(backend_alias, queue["name"], state),
45
+ value=queue[f"{state}_count"],
46
+ )
47
+ )
48
+ queue_paused.append(
49
+ MetricSample(
50
+ labels=(backend_alias, queue["name"]),
51
+ value=1 if queue["paused"] else 0,
52
+ )
53
+ )
54
+ if queue["latency_seconds"] is not None:
55
+ queue_latency.append(
56
+ MetricSample(
57
+ labels=(backend_alias, queue["name"]),
58
+ value=queue["latency_seconds"],
59
+ )
60
+ )
61
+ queue_workers.append(
62
+ MetricSample(
63
+ labels=(backend_alias, queue["name"]),
64
+ value=queue["live_worker_count"],
65
+ )
66
+ )
67
+
68
+ for status in ("live", "stale"):
69
+ runner_processes.append(
70
+ MetricSample(
71
+ labels=(backend_alias, status),
72
+ value=runner_metrics[status],
73
+ )
74
+ )
75
+ for kind, counts in sorted(runner_metrics["by_kind"].items()):
76
+ for status in ("live", "stale"):
77
+ runner_processes_by_kind.append(
78
+ MetricSample(
79
+ labels=(backend_alias, kind, status),
80
+ value=counts.get(status, 0),
81
+ )
82
+ )
83
+
84
+ recurring_tasks.append(
85
+ MetricSample(
86
+ labels=(backend_alias,),
87
+ value=len(snapshot["recurring_rows"]),
88
+ )
89
+ )
90
+ process_rows.append(
91
+ MetricSample(
92
+ labels=(backend_alias,),
93
+ value=len(snapshot["process_rows"]),
94
+ )
95
+ )
96
+
97
+ if queue_database_alias in seen_queue_databases:
98
+ continue
99
+ seen_queue_databases.add(queue_database_alias)
100
+ semaphores.append(
101
+ MetricSample(
102
+ labels=(queue_database_alias,),
103
+ value=len(snapshot["semaphore_rows"]),
104
+ )
105
+ )
106
+
107
+ return (
108
+ MetricFamily(
109
+ name="dj_queue_queue_jobs",
110
+ help_text="Current job count by backend, queue, and state",
111
+ labels=("backend", "queue", "state"),
112
+ samples=tuple(queue_jobs),
113
+ ),
114
+ MetricFamily(
115
+ name="dj_queue_queue_paused",
116
+ help_text="Whether a queue is paused for a backend",
117
+ labels=("backend", "queue"),
118
+ samples=tuple(queue_paused),
119
+ ),
120
+ MetricFamily(
121
+ name="dj_queue_queue_latency_seconds",
122
+ help_text="Latency of the oldest ready job in a backend queue",
123
+ labels=("backend", "queue"),
124
+ samples=tuple(queue_latency),
125
+ ),
126
+ MetricFamily(
127
+ name="dj_queue_queue_live_workers",
128
+ help_text="Live workers that can service a backend queue",
129
+ labels=("backend", "queue"),
130
+ samples=tuple(queue_workers),
131
+ ),
132
+ MetricFamily(
133
+ name="dj_queue_runner_processes",
134
+ help_text="Current runner process count by backend and liveness",
135
+ labels=("backend", "status"),
136
+ samples=tuple(runner_processes),
137
+ ),
138
+ MetricFamily(
139
+ name="dj_queue_runner_processes_by_kind",
140
+ help_text="Current runner process count by backend, kind, and liveness",
141
+ labels=("backend", "kind", "status"),
142
+ samples=tuple(runner_processes_by_kind),
143
+ ),
144
+ MetricFamily(
145
+ name="dj_queue_recurring_tasks",
146
+ help_text="Current recurring task count by backend",
147
+ labels=("backend",),
148
+ samples=tuple(recurring_tasks),
149
+ ),
150
+ MetricFamily(
151
+ name="dj_queue_semaphores",
152
+ help_text="Current semaphore count by queue database",
153
+ labels=("queue_database",),
154
+ samples=tuple(semaphores),
155
+ ),
156
+ MetricFamily(
157
+ name="dj_queue_process_rows",
158
+ help_text="Current process row count by backend",
159
+ labels=("backend",),
160
+ samples=tuple(process_rows),
161
+ ),
162
+ )
@@ -25,6 +25,7 @@ from dj_queue.models import (
25
25
  ScheduledExecution,
26
26
  Semaphore,
27
27
  )
28
+ from dj_queue.queue_state import queue_state_counts
28
29
 
29
30
 
30
31
  @dataclass(frozen=True, slots=True)
@@ -414,22 +415,6 @@ def semaphore_rows_for_backend(*, backend_alias):
414
415
  ]
415
416
 
416
417
 
417
- def queue_state_counts(*, backend_alias, queue_name):
418
- alias = get_database_alias(backend_alias)
419
- base_queryset = Job.objects.using(alias).filter(
420
- backend_alias=backend_alias,
421
- queue_name=queue_name,
422
- )
423
- return {
424
- "ready": base_queryset.filter(ready_execution__isnull=False).count(),
425
- "claimed": base_queryset.filter(claimed_execution__isnull=False).count(),
426
- "scheduled": base_queryset.filter(scheduled_execution__isnull=False).count(),
427
- "blocked": base_queryset.filter(blocked_execution__isnull=False).count(),
428
- "failed": base_queryset.filter(failed_execution__isnull=False).count(),
429
- "finished": base_queryset.filter(finished_at__isnull=False).count(),
430
- }
431
-
432
-
433
418
  def next_run_at(schedule, now):
434
419
  return next_cron_run(schedule, now)
435
420
 
@@ -0,0 +1,190 @@
1
+ import json
2
+
3
+ from dj_queue.db import database_capabilities
4
+ from dj_queue.exceptions import EnqueueError
5
+ from dj_queue.models import BlockedExecution, Pause, ReadyExecution, ScheduledExecution
6
+
7
+
8
+ def _normalize_payload(args, kwargs):
9
+ try:
10
+ return json.loads(json.dumps({"args": list(args), "kwargs": dict(kwargs)}))
11
+ except (TypeError, ValueError) as exc:
12
+ raise EnqueueError("payload must be JSON round-trippable") from exc
13
+
14
+
15
+ def _task_option(task, name, default=None):
16
+ if hasattr(task, name):
17
+ return getattr(task, name)
18
+ return getattr(task.func, name, default)
19
+
20
+
21
+ def _lock_active_pauses(alias, backend_alias, queue_names=None):
22
+ queryset = Pause.objects.using(alias).select_for_update().filter(backend_alias=backend_alias)
23
+ if queue_names is not None:
24
+ active_queue_names = tuple(queue_name for queue_name in queue_names if queue_name)
25
+ if not active_queue_names:
26
+ return set()
27
+ queryset = queryset.filter(queue_name__in=active_queue_names)
28
+ return set(queryset.values_list("queue_name", flat=True))
29
+
30
+
31
+ def _ready_execution_row(
32
+ job,
33
+ *,
34
+ backend_alias,
35
+ queue_name=None,
36
+ priority=None,
37
+ ready_at=None,
38
+ created_at=None,
39
+ ):
40
+ return ReadyExecution(
41
+ **_ready_execution_fields(
42
+ job,
43
+ backend_alias=backend_alias,
44
+ queue_name=queue_name,
45
+ priority=priority,
46
+ ready_at=ready_at,
47
+ created_at=created_at,
48
+ )
49
+ )
50
+
51
+
52
+ def _create_ready_execution(
53
+ alias,
54
+ job,
55
+ *,
56
+ backend_alias,
57
+ queue_name=None,
58
+ priority=None,
59
+ ready_at=None,
60
+ created_at=None,
61
+ ):
62
+ queue_name = _execution_queue_name(job, queue_name)
63
+ _lock_active_pauses(alias, backend_alias, {queue_name})
64
+ return ReadyExecution.objects.using(alias).create(
65
+ **_ready_execution_fields(
66
+ job,
67
+ backend_alias=backend_alias,
68
+ queue_name=queue_name,
69
+ priority=priority,
70
+ ready_at=ready_at,
71
+ created_at=created_at,
72
+ )
73
+ )
74
+
75
+
76
+ def _scheduled_execution_row(job, *, backend_alias, scheduled_at=None, created_at=None):
77
+ return ScheduledExecution(
78
+ **_scheduled_execution_fields(
79
+ job,
80
+ backend_alias=backend_alias,
81
+ scheduled_at=scheduled_at,
82
+ created_at=created_at,
83
+ )
84
+ )
85
+
86
+
87
+ def _create_scheduled_execution(alias, job, *, backend_alias, scheduled_at=None):
88
+ return ScheduledExecution.objects.using(alias).create(
89
+ **_scheduled_execution_fields(
90
+ job,
91
+ backend_alias=backend_alias,
92
+ scheduled_at=scheduled_at,
93
+ )
94
+ )
95
+
96
+
97
+ def _create_blocked_execution(
98
+ alias,
99
+ job,
100
+ *,
101
+ backend_alias,
102
+ concurrency_key=None,
103
+ expires_at,
104
+ queue_name=None,
105
+ priority=None,
106
+ ):
107
+ return BlockedExecution.objects.using(alias).create(
108
+ **_blocked_execution_fields(
109
+ job,
110
+ backend_alias=backend_alias,
111
+ concurrency_key=concurrency_key,
112
+ expires_at=expires_at,
113
+ queue_name=queue_name,
114
+ priority=priority,
115
+ )
116
+ )
117
+
118
+
119
+ def _ready_execution_fields(
120
+ job,
121
+ *,
122
+ backend_alias,
123
+ queue_name=None,
124
+ priority=None,
125
+ ready_at=None,
126
+ created_at=None,
127
+ ):
128
+ fields = {
129
+ "job": job,
130
+ "backend_alias": backend_alias,
131
+ "queue_name": _execution_queue_name(job, queue_name),
132
+ "priority": _execution_priority(job, priority),
133
+ "latency_started_at": ready_at,
134
+ }
135
+ if created_at is not None:
136
+ fields["created_at"] = created_at
137
+ return fields
138
+
139
+
140
+ def _scheduled_execution_fields(job, *, backend_alias, scheduled_at=None, created_at=None):
141
+ fields = {
142
+ "job": job,
143
+ "backend_alias": backend_alias,
144
+ "queue_name": job.queue_name,
145
+ "priority": job.priority,
146
+ "scheduled_at": scheduled_at if scheduled_at is not None else job.scheduled_at,
147
+ }
148
+ if created_at is not None:
149
+ fields["created_at"] = created_at
150
+ return fields
151
+
152
+
153
+ def _blocked_execution_fields(
154
+ job,
155
+ *,
156
+ backend_alias,
157
+ concurrency_key=None,
158
+ expires_at,
159
+ queue_name=None,
160
+ priority=None,
161
+ ):
162
+ return {
163
+ "job": job,
164
+ "backend_alias": backend_alias,
165
+ "queue_name": _execution_queue_name(job, queue_name),
166
+ "priority": _execution_priority(job, priority),
167
+ "concurrency_key": concurrency_key if concurrency_key is not None else job.concurrency_key,
168
+ "expires_at": expires_at,
169
+ }
170
+
171
+
172
+ def _execution_queue_name(job, queue_name):
173
+ return job.queue_name if queue_name is None else queue_name
174
+
175
+
176
+ def _execution_priority(job, priority):
177
+ return job.priority if priority is None else priority
178
+
179
+
180
+ def _consume_selected_rows(alias, model, rows):
181
+ if not database_capabilities(alias).uses_serialized_writes:
182
+ model.objects.using(alias).filter(pk__in=[row.pk for row in rows]).delete()
183
+ return rows
184
+
185
+ consumed_rows = []
186
+ for row in rows:
187
+ deleted, _ = model.objects.using(alias).filter(pk=row.pk).delete()
188
+ if deleted:
189
+ consumed_rows.append(row)
190
+ return consumed_rows
@@ -12,7 +12,8 @@ from dj_queue.log import log_event
12
12
  from dj_queue.models import BlockedExecution, ClaimedExecution, ReadyExecution, Semaphore
13
13
  from dj_queue.operations._helpers import (
14
14
  _consume_selected_rows,
15
- _lock_active_pauses,
15
+ _create_blocked_execution,
16
+ _create_ready_execution,
16
17
  _task_option,
17
18
  )
18
19
  from dj_queue.operations._insert import create_ignore_conflicts
@@ -170,13 +171,13 @@ def unblock_next_blocked_job(
170
171
  queue_name = blocked.queue_name
171
172
  priority = blocked.priority
172
173
  blocked.delete(using=alias)
173
- _lock_active_pauses(alias, backend_alias, {queue_name})
174
- ReadyExecution.objects.using(alias).create(
174
+ _create_ready_execution(
175
+ alias,
175
176
  job=job,
176
177
  backend_alias=backend_alias,
177
178
  queue_name=queue_name,
178
179
  priority=priority,
179
- latency_started_at=now,
180
+ ready_at=now,
180
181
  )
181
182
 
182
183
  log_event(
@@ -258,20 +259,21 @@ def promote_expired_blocked_jobs(*, batch_size=500, backend_alias="default", use
258
259
  priority = blocked.priority
259
260
  if not uses_serialized_writes:
260
261
  blocked.delete(using=alias)
261
- _lock_active_pauses(alias, backend_alias, {queue_name})
262
- ReadyExecution.objects.using(alias).create(
262
+ _create_ready_execution(
263
+ alias,
263
264
  job=job,
264
265
  backend_alias=backend_alias,
265
266
  queue_name=queue_name,
266
267
  priority=priority,
267
- latency_started_at=now,
268
+ ready_at=now,
268
269
  )
269
270
  promoted_jobs.append(job)
270
271
  else:
271
272
  expires_at = now + timedelta(seconds=duration_seconds)
272
273
  if uses_serialized_writes:
273
- BlockedExecution.objects.using(alias).create(
274
- job=job,
274
+ _create_blocked_execution(
275
+ alias,
276
+ job,
275
277
  backend_alias=backend_alias,
276
278
  queue_name=blocked.queue_name,
277
279
  priority=blocked.priority,