dj-queue 0.2.3__tar.gz → 0.2.4__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.
- {dj_queue-0.2.3 → dj_queue-0.2.4}/PKG-INFO +6 -3
- {dj_queue-0.2.3 → dj_queue-0.2.4}/README.md +5 -2
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/admin.py +65 -9
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/dashboard.py +18 -4
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/operations/jobs.py +17 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/templates/admin/dj_queue/change_form.html +1 -1
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/templates/admin/dj_queue/queue_jobs.html +2 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/pyproject.toml +1 -1
- {dj_queue-0.2.3 → dj_queue-0.2.4}/LICENSE +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/__init__.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/api.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/apps.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/backend.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/config.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/contrib/__init__.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/contrib/asgi.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/contrib/gunicorn.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/db.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/exceptions.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/hooks.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/log.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/management/__init__.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/management/commands/__init__.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/management/commands/dj_queue.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/management/commands/dj_queue_health.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/management/commands/dj_queue_prune.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/migrations/0001_initial.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/migrations/0002_pause_semaphore.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/migrations/0003_recurringtask_recurringexecution.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/migrations/0004_dashboard.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/migrations/__init__.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/models/__init__.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/models/jobs.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/models/recurring.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/models/runtime.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/operations/__init__.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/operations/cleanup.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/operations/concurrency.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/operations/recurring.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/routers.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/runtime/__init__.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/runtime/base.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/runtime/dispatcher.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/runtime/errors.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/runtime/interruptible.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/runtime/notify.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/runtime/pidfile.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/runtime/pool.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/runtime/procline.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/runtime/scheduler.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/runtime/supervisor.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/runtime/worker.py +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/templates/admin/dj_queue/change_list.html +0 -0
- {dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/templates/admin/dj_queue/dashboard.html +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dj-queue
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Database-backed task queue backend for Django's django.tasks framework
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -164,6 +164,8 @@ If Django admin is installed, `dj_queue` adds an operator dashboard at
|
|
|
164
164
|
- queue, process, recurring-task, and semaphore overview
|
|
165
165
|
- backend-aware dashboard and raw changelists
|
|
166
166
|
- queue controls: pause, resume, clear ready
|
|
167
|
+
- job detail action: enqueue a fresh copy of any stored job
|
|
168
|
+
- pause detail action: resume the paused queue from the raw pause row
|
|
167
169
|
- failed-job actions: retry and discard from list and detail views
|
|
168
170
|
- queue drill-down pages for state-specific inspection
|
|
169
171
|
|
|
@@ -430,8 +432,9 @@ python manage.py dj_queue_prune --task-path myapp.tasks.cleanup
|
|
|
430
432
|
When a task raises, `dj_queue` keeps the job and its failed execution row in the
|
|
431
433
|
queue database, including the exception class, message, and traceback.
|
|
432
434
|
|
|
433
|
-
You can retry and discard failed jobs through Django admin,
|
|
434
|
-
|
|
435
|
+
You can retry and discard failed jobs through Django admin, and any raw job
|
|
436
|
+
detail page can enqueue a fresh copy of that stored job. The failed-job actions
|
|
437
|
+
also stay available directly through the operations layer:
|
|
435
438
|
|
|
436
439
|
```python
|
|
437
440
|
from dj_queue.operations.jobs import discard_failed_job, retry_failed_job
|
|
@@ -138,6 +138,8 @@ If Django admin is installed, `dj_queue` adds an operator dashboard at
|
|
|
138
138
|
- queue, process, recurring-task, and semaphore overview
|
|
139
139
|
- backend-aware dashboard and raw changelists
|
|
140
140
|
- queue controls: pause, resume, clear ready
|
|
141
|
+
- job detail action: enqueue a fresh copy of any stored job
|
|
142
|
+
- pause detail action: resume the paused queue from the raw pause row
|
|
141
143
|
- failed-job actions: retry and discard from list and detail views
|
|
142
144
|
- queue drill-down pages for state-specific inspection
|
|
143
145
|
|
|
@@ -404,8 +406,9 @@ python manage.py dj_queue_prune --task-path myapp.tasks.cleanup
|
|
|
404
406
|
When a task raises, `dj_queue` keeps the job and its failed execution row in the
|
|
405
407
|
queue database, including the exception class, message, and traceback.
|
|
406
408
|
|
|
407
|
-
You can retry and discard failed jobs through Django admin,
|
|
408
|
-
|
|
409
|
+
You can retry and discard failed jobs through Django admin, and any raw job
|
|
410
|
+
detail page can enqueue a fresh copy of that stored job. The failed-job actions
|
|
411
|
+
also stay available directly through the operations layer:
|
|
409
412
|
|
|
410
413
|
```python
|
|
411
414
|
from dj_queue.operations.jobs import discard_failed_job, retry_failed_job
|
|
@@ -15,6 +15,7 @@ from django.utils import timezone
|
|
|
15
15
|
|
|
16
16
|
from dj_queue.config import load_backend_config
|
|
17
17
|
from dj_queue import dashboard
|
|
18
|
+
from dj_queue.api import QueueInfo
|
|
18
19
|
from dj_queue.db import get_database_alias
|
|
19
20
|
from dj_queue.models import (
|
|
20
21
|
BlockedExecution,
|
|
@@ -26,6 +27,7 @@ from dj_queue.models import (
|
|
|
26
27
|
RecurringTask,
|
|
27
28
|
Semaphore,
|
|
28
29
|
)
|
|
30
|
+
from dj_queue.operations.jobs import enqueue_job_again
|
|
29
31
|
|
|
30
32
|
|
|
31
33
|
class DjQueueFirstAdminSite(admin.AdminSite):
|
|
@@ -214,6 +216,7 @@ class HiddenSidebarAdminMixin:
|
|
|
214
216
|
extra_context = {
|
|
215
217
|
**(extra_context or {}),
|
|
216
218
|
"dashboard_url": self._dashboard_url(request),
|
|
219
|
+
"changelist_url": self._changelist_url(backend_alias=self._backend_alias(request)),
|
|
217
220
|
"change_actions": self.get_change_actions(request, obj),
|
|
218
221
|
}
|
|
219
222
|
return super().changeform_view(
|
|
@@ -474,18 +477,49 @@ class JobAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
|
|
|
474
477
|
return format_html('<a href="{}">{}</a>', url, obj.queue_name)
|
|
475
478
|
|
|
476
479
|
def get_change_actions(self, request, obj):
|
|
477
|
-
if obj is None
|
|
480
|
+
if obj is None:
|
|
478
481
|
return ()
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
482
|
+
actions = [{"name": "enqueue", "label": "Enqueue job", "css_class": "djq-object-action-retry"}]
|
|
483
|
+
if obj.status == "failed":
|
|
484
|
+
actions.extend(
|
|
485
|
+
(
|
|
486
|
+
{
|
|
487
|
+
"name": "retry",
|
|
488
|
+
"label": "Retry failed job",
|
|
489
|
+
"css_class": "djq-object-action-retry",
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
"name": "discard",
|
|
493
|
+
"label": "Discard failed job",
|
|
494
|
+
"css_class": "djq-object-action-discard",
|
|
495
|
+
},
|
|
496
|
+
)
|
|
497
|
+
)
|
|
498
|
+
return tuple(actions)
|
|
487
499
|
|
|
488
500
|
def handle_change_action(self, request, obj, action):
|
|
501
|
+
if action == "enqueue":
|
|
502
|
+
try:
|
|
503
|
+
new_job = enqueue_job_again(obj.pk, backend_alias=obj.backend_name)
|
|
504
|
+
except Exception as exc:
|
|
505
|
+
self.message_user(request, f"Could not enqueue job: {exc}", level=messages.ERROR)
|
|
506
|
+
return HttpResponseRedirect(
|
|
507
|
+
self._change_url(object_id=obj.pk, backend_alias=obj.backend_name)
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
self.message_user(
|
|
511
|
+
request,
|
|
512
|
+
format_html(
|
|
513
|
+
'Enqueued job <a href="{}">{}</a>.',
|
|
514
|
+
self._change_url(object_id=new_job.pk, backend_alias=new_job.backend_name),
|
|
515
|
+
new_job.pk,
|
|
516
|
+
),
|
|
517
|
+
level=messages.SUCCESS,
|
|
518
|
+
)
|
|
519
|
+
return HttpResponseRedirect(
|
|
520
|
+
self._change_url(object_id=obj.pk, backend_alias=obj.backend_name)
|
|
521
|
+
)
|
|
522
|
+
|
|
489
523
|
if obj.status != "failed":
|
|
490
524
|
self.message_user(request, "This job is not failed", level=messages.ERROR)
|
|
491
525
|
return HttpResponseRedirect(
|
|
@@ -633,6 +667,28 @@ class PauseAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
|
|
|
633
667
|
readonly_fields = ("queue_name", "created_at")
|
|
634
668
|
search_fields = ("queue_name",)
|
|
635
669
|
|
|
670
|
+
def get_change_actions(self, request, obj):
|
|
671
|
+
if obj is None:
|
|
672
|
+
return ()
|
|
673
|
+
return ({"name": "resume", "label": "Resume queue", "css_class": "djq-object-action-retry"},)
|
|
674
|
+
|
|
675
|
+
def handle_change_action(self, request, obj, action):
|
|
676
|
+
backend_alias = self._backend_alias(request)
|
|
677
|
+
if action == "resume":
|
|
678
|
+
QueueInfo(obj.queue_name, backend_alias=backend_alias).resume()
|
|
679
|
+
self.message_user(
|
|
680
|
+
request,
|
|
681
|
+
format_html(
|
|
682
|
+
'Resumed queue <a href="{}">{}</a>',
|
|
683
|
+
f"{reverse('admin:dj_queue_dashboard_queue', args=[obj.queue_name])}?{urlencode({'backend': backend_alias})}",
|
|
684
|
+
obj.queue_name,
|
|
685
|
+
),
|
|
686
|
+
level=messages.SUCCESS,
|
|
687
|
+
)
|
|
688
|
+
return HttpResponseRedirect(self._changelist_url(backend_alias=backend_alias))
|
|
689
|
+
|
|
690
|
+
return HttpResponseRedirect(self._change_url(object_id=obj.pk, backend_alias=backend_alias))
|
|
691
|
+
|
|
636
692
|
|
|
637
693
|
@admin.register(Semaphore)
|
|
638
694
|
class SemaphoreAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
|
|
@@ -385,7 +385,14 @@ def queue_page_context(*, backend_alias, queue_name, state, page_number, query_p
|
|
|
385
385
|
paginator = Paginator(jobs, PAGE_SIZE)
|
|
386
386
|
page_obj = paginator.get_page(query_params.get("page", page_number))
|
|
387
387
|
queue_info = QueueInfo(queue_name, backend_alias=backend_alias)
|
|
388
|
+
queue_paused = queue_info.paused
|
|
389
|
+
queue_latency_seconds = None if queue_paused else queue_info.latency
|
|
388
390
|
state_counts = _queue_state_counts(backend_alias=backend_alias, queue_name=queue_name)
|
|
391
|
+
live_workers = [
|
|
392
|
+
process
|
|
393
|
+
for process in Process.objects.using(alias).filter(kind="Worker")
|
|
394
|
+
if process.last_heartbeat_at >= process_cutoff
|
|
395
|
+
]
|
|
389
396
|
state_tabs = [
|
|
390
397
|
{
|
|
391
398
|
"name": state_name,
|
|
@@ -425,7 +432,13 @@ def queue_page_context(*, backend_alias, queue_name, state, page_number, query_p
|
|
|
425
432
|
"queue_database_alias": alias,
|
|
426
433
|
"queue_name": queue_name,
|
|
427
434
|
"queue_info": queue_info,
|
|
428
|
-
"queue_paused":
|
|
435
|
+
"queue_paused": queue_paused,
|
|
436
|
+
"queue_latency_seconds": queue_latency_seconds,
|
|
437
|
+
"queue_worker_count": sum(
|
|
438
|
+
1
|
|
439
|
+
for worker in live_workers
|
|
440
|
+
if _queue_matches_selectors(queue_name, worker.metadata.get("queues", []))
|
|
441
|
+
),
|
|
429
442
|
"state": state,
|
|
430
443
|
"state_label": QUEUE_STATE_LABELS[state],
|
|
431
444
|
"state_tabs": state_tabs,
|
|
@@ -1224,9 +1237,10 @@ def _queue_rows(*, backend_alias, now, process_cutoff):
|
|
|
1224
1237
|
|
|
1225
1238
|
rows = []
|
|
1226
1239
|
for queue_name in sorted(queue_names):
|
|
1240
|
+
paused = queue_name in paused_queues
|
|
1227
1241
|
oldest_ready_at = oldest_ready.get(queue_name)
|
|
1228
1242
|
latency_seconds = None
|
|
1229
|
-
if oldest_ready_at is not None:
|
|
1243
|
+
if oldest_ready_at is not None and paused is False:
|
|
1230
1244
|
latency_seconds = max((now - oldest_ready_at).total_seconds(), 0.0)
|
|
1231
1245
|
|
|
1232
1246
|
ready_count = ready_counts.get(queue_name, 0)
|
|
@@ -1236,7 +1250,7 @@ def _queue_rows(*, backend_alias, now, process_cutoff):
|
|
|
1236
1250
|
failed_count = failed_counts.get(queue_name, 0)
|
|
1237
1251
|
finished_count = finished_counts.get(queue_name, 0)
|
|
1238
1252
|
shared_sources = []
|
|
1239
|
-
if
|
|
1253
|
+
if paused:
|
|
1240
1254
|
shared_sources.append("pause")
|
|
1241
1255
|
if queue_name in recurring_queues:
|
|
1242
1256
|
shared_sources.append("recurring task")
|
|
@@ -1250,7 +1264,7 @@ def _queue_rows(*, backend_alias, now, process_cutoff):
|
|
|
1250
1264
|
"blocked_count": blocked_count,
|
|
1251
1265
|
"failed_count": failed_count,
|
|
1252
1266
|
"finished_count": finished_count,
|
|
1253
|
-
"paused":
|
|
1267
|
+
"paused": paused,
|
|
1254
1268
|
"latency_seconds": latency_seconds,
|
|
1255
1269
|
"oldest_scheduled_at": oldest_scheduled.get(queue_name),
|
|
1256
1270
|
"oldest_blocked_at": oldest_blocked.get(queue_name),
|
|
@@ -354,6 +354,23 @@ def retry_failed_job(job_id, *, backend_alias="default"):
|
|
|
354
354
|
return job
|
|
355
355
|
|
|
356
356
|
|
|
357
|
+
def enqueue_job_again(job_id, *, backend_alias="default"):
|
|
358
|
+
alias = get_database_alias(backend_alias)
|
|
359
|
+
source_job = Job.objects.using(alias).get(pk=job_id)
|
|
360
|
+
task = import_string(source_job.task_path)
|
|
361
|
+
if hasattr(task, "using"):
|
|
362
|
+
task = task.using(
|
|
363
|
+
priority=source_job.priority,
|
|
364
|
+
queue_name=source_job.queue_name,
|
|
365
|
+
run_after=source_job.scheduled_at,
|
|
366
|
+
backend=source_job.backend_name,
|
|
367
|
+
)
|
|
368
|
+
args = list(source_job.payload.get("args", []))
|
|
369
|
+
kwargs = dict(source_job.payload.get("kwargs", {}))
|
|
370
|
+
job, _ = enqueue_job_with_dispatch(task, args, kwargs, backend_alias=source_job.backend_name)
|
|
371
|
+
return job
|
|
372
|
+
|
|
373
|
+
|
|
357
374
|
def discard_failed_job(job_id, *, backend_alias="default"):
|
|
358
375
|
alias = get_database_alias(backend_alias)
|
|
359
376
|
queryset = Job.objects.using(alias).filter(pk=job_id, failed_execution__isnull=False)
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
<div class="breadcrumbs">
|
|
47
47
|
<a href='{% url "admin:index" %}'>{% translate "Home" %}</a>
|
|
48
48
|
› <a href="{{ dashboard_url }}">dj_queue</a>
|
|
49
|
-
› {% if has_view_permission %}<a href="{
|
|
49
|
+
› {% if has_view_permission %}<a href="{{ changelist_url }}">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %}
|
|
50
50
|
› {% if add %}{% blocktranslate with name=opts.verbose_name %}Add {{ name }}{% endblocktranslate %}{% else %}{{ original|truncatewords:"18" }}{% endif %}
|
|
51
51
|
</div>
|
|
52
52
|
{% endblock breadcrumbs %}
|
|
@@ -216,6 +216,8 @@
|
|
|
216
216
|
<div class="queue-toolbar-meta">
|
|
217
217
|
<span><strong>Backend:</strong> {{ backend_alias }}</span>
|
|
218
218
|
<span><strong>Database:</strong> {{ queue_database_alias }}</span>
|
|
219
|
+
<span><strong>Workers:</strong> {{ queue_worker_count }}</span>
|
|
220
|
+
<span><strong>Latency:</strong> {% if queue_latency_seconds != None %}{{ queue_latency_seconds|floatformat:1 }}s{% else %}-{% endif %}</span>
|
|
219
221
|
<span><strong>Paused:</strong> {{ queue_paused|yesno:"yes,no" }}</span>
|
|
220
222
|
</div>
|
|
221
223
|
<div class="queue-toolbar-actions">
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dj_queue-0.2.3 → dj_queue-0.2.4}/dj_queue/migrations/0003_recurringtask_recurringexecution.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|