dj-queue 0.5.0__tar.gz → 0.5.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 (69) hide show
  1. {dj_queue-0.5.0 → dj_queue-0.5.1}/PKG-INFO +52 -37
  2. {dj_queue-0.5.0 → dj_queue-0.5.1}/README.md +51 -36
  3. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/admin.py +5 -1
  4. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/api.py +24 -11
  5. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/contrib/asgi.py +15 -0
  6. {dj_queue-0.5.0 → dj_queue-0.5.1}/pyproject.toml +1 -1
  7. {dj_queue-0.5.0 → dj_queue-0.5.1}/LICENSE +0 -0
  8. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/__init__.py +0 -0
  9. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/apps.py +0 -0
  10. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/backend.py +0 -0
  11. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/config.py +0 -0
  12. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/contrib/__init__.py +0 -0
  13. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/contrib/gunicorn.py +0 -0
  14. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/contrib/prometheus.py +0 -0
  15. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/dashboard.py +0 -0
  16. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/db.py +0 -0
  17. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/exceptions.py +0 -0
  18. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/hooks.py +0 -0
  19. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/log.py +0 -0
  20. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/management/__init__.py +0 -0
  21. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/management/commands/__init__.py +0 -0
  22. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/management/commands/dj_queue.py +0 -0
  23. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/management/commands/dj_queue_health.py +0 -0
  24. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/management/commands/dj_queue_prune.py +0 -0
  25. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/migrations/0001_initial.py +0 -0
  26. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/migrations/0002_pause_semaphore.py +0 -0
  27. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/migrations/0003_recurringtask_recurringexecution.py +0 -0
  28. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/migrations/0004_dashboard.py +0 -0
  29. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/migrations/0005_remove_recurringexecution_dj_queue_recurring_executions_task_key_run_at_unique_and_more.py +0 -0
  30. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/migrations/__init__.py +0 -0
  31. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/models/__init__.py +0 -0
  32. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/models/jobs.py +0 -0
  33. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/models/recurring.py +0 -0
  34. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/models/runtime.py +0 -0
  35. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/observability.py +0 -0
  36. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/operations/__init__.py +0 -0
  37. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/operations/cleanup.py +0 -0
  38. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/operations/concurrency.py +0 -0
  39. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/operations/jobs.py +0 -0
  40. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/operations/recurring.py +0 -0
  41. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/routers.py +0 -0
  42. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/runtime/__init__.py +0 -0
  43. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/runtime/base.py +0 -0
  44. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/runtime/dispatcher.py +0 -0
  45. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/runtime/errors.py +0 -0
  46. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/runtime/interruptible.py +0 -0
  47. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/runtime/notify.py +0 -0
  48. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/runtime/pidfile.py +0 -0
  49. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/runtime/pool.py +0 -0
  50. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/runtime/procline.py +0 -0
  51. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/runtime/scheduler.py +0 -0
  52. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/runtime/supervisor.py +0 -0
  53. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/runtime/worker.py +0 -0
  54. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/templates/admin/dj_queue/_dashboard_process_rows.html +0 -0
  55. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/templates/admin/dj_queue/_dashboard_recurring_rows.html +0 -0
  56. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/templates/admin/dj_queue/_dashboard_section_table.html +0 -0
  57. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/templates/admin/dj_queue/_dashboard_semaphore_rows.html +0 -0
  58. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/templates/admin/dj_queue/_paginator.html +0 -0
  59. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/templates/admin/dj_queue/_queue_controls.html +0 -0
  60. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/templates/admin/dj_queue/_sortable_header_cells.html +0 -0
  61. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/templates/admin/dj_queue/change_form.html +0 -0
  62. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/templates/admin/dj_queue/change_list.html +0 -0
  63. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/templates/admin/dj_queue/dashboard.html +0 -0
  64. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/templates/admin/dj_queue/includes/fieldset.html +0 -0
  65. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/templates/admin/dj_queue/queue_jobs.html +0 -0
  66. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/templatetags/__init__.py +0 -0
  67. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/templatetags/dj_queue_admin.py +0 -0
  68. {dj_queue-0.5.0 → dj_queue-0.5.1}/dj_queue/urls.py +0 -0
  69. {dj_queue-0.5.0 → dj_queue-0.5.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.5.0
3
+ Version: 0.5.1
4
4
  Summary: Database-backed task queue backend for Django’s Tasks framework.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -549,6 +549,41 @@ except EnqueueError as exc:
549
549
  Task execution errors are different: they become failed jobs and stay
550
550
  inspectable in the queue database.
551
551
 
552
+ ## Lifecycle Hooks
553
+
554
+ Register hooks before starting the runtime, typically during Django startup.
555
+ Each callback receives the live supervisor or runner instance.
556
+
557
+ ```python
558
+ from dj_queue.hooks import on_start, on_worker_start, register_hook
559
+
560
+ @on_start
561
+ def supervisor_started(process):
562
+ print(process.name)
563
+
564
+ @on_worker_start
565
+ def worker_started(process):
566
+ print(process.metadata)
567
+
568
+ @register_hook("scheduler.exit")
569
+ def scheduler_exited(process):
570
+ print(process.name)
571
+ ```
572
+
573
+ Available hook helpers:
574
+
575
+ - supervisor: `on_start`, `on_stop`, `on_exit`
576
+ - worker: `on_worker_start`, `on_worker_stop`, `on_worker_exit`
577
+ - dispatcher: `on_dispatcher_start`, `on_dispatcher_stop`, `on_dispatcher_exit`
578
+ - scheduler: `on_scheduler_start`, `on_scheduler_stop`, `on_scheduler_exit`
579
+ - generic events: `register_hook("worker.start")`, `register_hook("dispatcher.stop")`, and so on
580
+
581
+ Hook notes:
582
+
583
+ - hooks fire in registration order
584
+ - hook failures do not block later hooks
585
+ - hook failures are isolated and routed through `on_thread_error`
586
+
552
587
  ## Multi-Database Setup
553
588
 
554
589
  `dj_queue` can keep queue tables on a dedicated database alias.
@@ -644,6 +679,21 @@ signal handling to the host server.
644
679
 
645
680
  ## Configuration
646
681
 
682
+ ### Queues, backends, and databases
683
+
684
+ `dj_queue` has three separate routing concepts. Keep them distinct:
685
+
686
+ - `queue_name`: what kind of work this job is. Use it to route lanes inside one backend, such as `email`, `webhooks`, or `search-index`.
687
+ - `backend_alias`: which logical queue system owns the work. Use it when you want separate runtime config, recurring tasks, pause and process visibility, retention, or admin scoping.
688
+ - `database_alias`: where that backend's queue tables and runtime activity live. Use it when you want a dedicated database connection path or stronger storage isolation.
689
+
690
+ Common setup choices:
691
+
692
+ - one backend, one database: simplest and usually enough
693
+ - one backend, separate queue database: good when you want dedicated queue connections
694
+ - multiple backends, same database: good for logical and operational separation without another database
695
+ - multiple backends, multiple databases: use when you need stronger isolation and accept more migration and deployment complexity
696
+
647
697
  ### Deployment topology
648
698
 
649
699
  Once migrations are in place, start processing jobs with `python manage.py dj_queue`
@@ -792,41 +842,6 @@ Environment overrides currently supported by `dj_queue` itself:
792
842
  - `DJ_QUEUE_MODE`
793
843
  - `DJ_QUEUE_SKIP_RECURRING`
794
844
 
795
- ## Lifecycle Hooks
796
-
797
- Register hooks before starting the runtime, typically during Django startup.
798
- Each callback receives the live supervisor or runner instance.
799
-
800
- ```python
801
- from dj_queue.hooks import on_start, on_worker_start, register_hook
802
-
803
- @on_start
804
- def supervisor_started(process):
805
- print(process.name)
806
-
807
- @on_worker_start
808
- def worker_started(process):
809
- print(process.metadata)
810
-
811
- @register_hook("scheduler.exit")
812
- def scheduler_exited(process):
813
- print(process.name)
814
- ```
815
-
816
- Available hook helpers:
817
-
818
- - supervisor: `on_start`, `on_stop`, `on_exit`
819
- - worker: `on_worker_start`, `on_worker_stop`, `on_worker_exit`
820
- - dispatcher: `on_dispatcher_start`, `on_dispatcher_stop`, `on_dispatcher_exit`
821
- - scheduler: `on_scheduler_start`, `on_scheduler_stop`, `on_scheduler_exit`
822
- - generic events: `register_hook("worker.start")`, `register_hook("dispatcher.stop")`, and so on
823
-
824
- Hook notes:
825
-
826
- - hooks fire in registration order
827
- - hook failures do not block later hooks
828
- - hook failures are isolated and routed through `on_thread_error`
829
-
830
845
  ### Runtime infrastructure errors
831
846
 
832
847
  Set `on_thread_error` to a dotted callable path when you want custom handling
@@ -852,7 +867,7 @@ become failed jobs instead.
852
867
  ## Monitoring
853
868
 
854
869
  Queue statistics are available in JSON via `/dj_queue/stats.json` and in
855
- Prometheus text format via `/dj_queue/metrics`.
870
+ Prometheus text format via `/dj_queue/metrics`.
856
871
 
857
872
  Include `dj_queue.urls` to expose them:
858
873
 
@@ -521,6 +521,41 @@ except EnqueueError as exc:
521
521
  Task execution errors are different: they become failed jobs and stay
522
522
  inspectable in the queue database.
523
523
 
524
+ ## Lifecycle Hooks
525
+
526
+ Register hooks before starting the runtime, typically during Django startup.
527
+ Each callback receives the live supervisor or runner instance.
528
+
529
+ ```python
530
+ from dj_queue.hooks import on_start, on_worker_start, register_hook
531
+
532
+ @on_start
533
+ def supervisor_started(process):
534
+ print(process.name)
535
+
536
+ @on_worker_start
537
+ def worker_started(process):
538
+ print(process.metadata)
539
+
540
+ @register_hook("scheduler.exit")
541
+ def scheduler_exited(process):
542
+ print(process.name)
543
+ ```
544
+
545
+ Available hook helpers:
546
+
547
+ - supervisor: `on_start`, `on_stop`, `on_exit`
548
+ - worker: `on_worker_start`, `on_worker_stop`, `on_worker_exit`
549
+ - dispatcher: `on_dispatcher_start`, `on_dispatcher_stop`, `on_dispatcher_exit`
550
+ - scheduler: `on_scheduler_start`, `on_scheduler_stop`, `on_scheduler_exit`
551
+ - generic events: `register_hook("worker.start")`, `register_hook("dispatcher.stop")`, and so on
552
+
553
+ Hook notes:
554
+
555
+ - hooks fire in registration order
556
+ - hook failures do not block later hooks
557
+ - hook failures are isolated and routed through `on_thread_error`
558
+
524
559
  ## Multi-Database Setup
525
560
 
526
561
  `dj_queue` can keep queue tables on a dedicated database alias.
@@ -616,6 +651,21 @@ signal handling to the host server.
616
651
 
617
652
  ## Configuration
618
653
 
654
+ ### Queues, backends, and databases
655
+
656
+ `dj_queue` has three separate routing concepts. Keep them distinct:
657
+
658
+ - `queue_name`: what kind of work this job is. Use it to route lanes inside one backend, such as `email`, `webhooks`, or `search-index`.
659
+ - `backend_alias`: which logical queue system owns the work. Use it when you want separate runtime config, recurring tasks, pause and process visibility, retention, or admin scoping.
660
+ - `database_alias`: where that backend's queue tables and runtime activity live. Use it when you want a dedicated database connection path or stronger storage isolation.
661
+
662
+ Common setup choices:
663
+
664
+ - one backend, one database: simplest and usually enough
665
+ - one backend, separate queue database: good when you want dedicated queue connections
666
+ - multiple backends, same database: good for logical and operational separation without another database
667
+ - multiple backends, multiple databases: use when you need stronger isolation and accept more migration and deployment complexity
668
+
619
669
  ### Deployment topology
620
670
 
621
671
  Once migrations are in place, start processing jobs with `python manage.py dj_queue`
@@ -764,41 +814,6 @@ Environment overrides currently supported by `dj_queue` itself:
764
814
  - `DJ_QUEUE_MODE`
765
815
  - `DJ_QUEUE_SKIP_RECURRING`
766
816
 
767
- ## Lifecycle Hooks
768
-
769
- Register hooks before starting the runtime, typically during Django startup.
770
- Each callback receives the live supervisor or runner instance.
771
-
772
- ```python
773
- from dj_queue.hooks import on_start, on_worker_start, register_hook
774
-
775
- @on_start
776
- def supervisor_started(process):
777
- print(process.name)
778
-
779
- @on_worker_start
780
- def worker_started(process):
781
- print(process.metadata)
782
-
783
- @register_hook("scheduler.exit")
784
- def scheduler_exited(process):
785
- print(process.name)
786
- ```
787
-
788
- Available hook helpers:
789
-
790
- - supervisor: `on_start`, `on_stop`, `on_exit`
791
- - worker: `on_worker_start`, `on_worker_stop`, `on_worker_exit`
792
- - dispatcher: `on_dispatcher_start`, `on_dispatcher_stop`, `on_dispatcher_exit`
793
- - scheduler: `on_scheduler_start`, `on_scheduler_stop`, `on_scheduler_exit`
794
- - generic events: `register_hook("worker.start")`, `register_hook("dispatcher.stop")`, and so on
795
-
796
- Hook notes:
797
-
798
- - hooks fire in registration order
799
- - hook failures do not block later hooks
800
- - hook failures are isolated and routed through `on_thread_error`
801
-
802
817
  ### Runtime infrastructure errors
803
818
 
804
819
  Set `on_thread_error` to a dotted callable path when you want custom handling
@@ -824,7 +839,7 @@ become failed jobs instead.
824
839
  ## Monitoring
825
840
 
826
841
  Queue statistics are available in JSON via `/dj_queue/stats.json` and in
827
- Prometheus text format via `/dj_queue/metrics`.
842
+ Prometheus text format via `/dj_queue/metrics`.
828
843
 
829
844
  Include `dj_queue.urls` to expose them:
830
845
 
@@ -458,8 +458,9 @@ class JobAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
458
458
  )
459
459
  readonly_fields = (
460
460
  "task_path",
461
- "queue_name",
461
+ "queue_name_link",
462
462
  "priority",
463
+ "display_status",
463
464
  "payload",
464
465
  "backend_alias",
465
466
  "scheduled_at",
@@ -475,6 +476,9 @@ class JobAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
475
476
  )
476
477
  search_fields = ("id", "task_path", "queue_name", "concurrency_key")
477
478
 
479
+ def get_fields(self, request, obj=None):
480
+ return self.get_readonly_fields(request, obj)
481
+
478
482
  def get_readonly_fields(self, request, obj=None):
479
483
  fields = super().get_readonly_fields(request, obj)
480
484
  if obj is None or obj.status != "failed":
@@ -1,5 +1,6 @@
1
1
  from functools import partial
2
2
 
3
+ from django.db.models import Case, DateTimeField, DurationField, ExpressionWrapper, F, Value, When
3
4
  from django.db.models.functions import Coalesce
4
5
  from django.db import transaction
5
6
  from django.utils import timezone
@@ -66,17 +67,29 @@ class QueueInfo:
66
67
 
67
68
  resumed_at = timezone.now()
68
69
  paused_at = pause.created_at
69
- ready_rows = list(
70
- ReadyExecution.objects.using(alias)
71
- .filter(queue_name=self.queue_name)
72
- .only("id", "created_at", "latency_started_at")
73
- )
74
- for ready_row in ready_rows:
75
- started_at = ready_row.latency_started_at or ready_row.created_at
76
- overlap_started_at = max(started_at, paused_at)
77
- ready_row.latency_started_at = started_at + (resumed_at - overlap_started_at)
78
- if ready_rows:
79
- ReadyExecution.objects.using(alias).bulk_update(ready_rows, ["latency_started_at"])
70
+ pause_duration = Value(resumed_at - paused_at, output_field=DurationField())
71
+ ready_row_ids = list(self._ready_queryset().values_list("id", flat=True))
72
+ if ready_row_ids:
73
+ ReadyExecution.objects.using(alias).filter(pk__in=ready_row_ids).update(
74
+ latency_started_at=Case(
75
+ When(
76
+ latency_started_at__isnull=True,
77
+ created_at__lt=paused_at,
78
+ then=ExpressionWrapper(
79
+ F("created_at") + pause_duration, output_field=DateTimeField()
80
+ ),
81
+ ),
82
+ When(
83
+ latency_started_at__lt=paused_at,
84
+ then=ExpressionWrapper(
85
+ F("latency_started_at") + pause_duration,
86
+ output_field=DateTimeField(),
87
+ ),
88
+ ),
89
+ default=Value(resumed_at, output_field=DateTimeField()),
90
+ output_field=DateTimeField(),
91
+ ),
92
+ )
80
93
  pause.delete()
81
94
 
82
95
  log_event("queue.resumed", backend_alias=self.backend_alias, queue_name=self.queue_name)
@@ -12,6 +12,12 @@ class DjQueueLifespan:
12
12
  self.app = app
13
13
  self.backend_alias = backend_alias
14
14
  self.supervisor = None
15
+ self._poll_task = None
16
+
17
+ async def _poll_supervisor(self):
18
+ while self.supervisor is not None:
19
+ await asyncio.to_thread(self.supervisor.poll_once)
20
+ await asyncio.sleep(self.supervisor.polling_interval)
15
21
 
16
22
  async def __call__(self, scope, receive, send):
17
23
  if scope["type"] != "lifespan":
@@ -23,8 +29,17 @@ class DjQueueLifespan:
23
29
  if message["type"] == "lifespan.startup":
24
30
  self.supervisor = build_supervisor(self.backend_alias)
25
31
  await asyncio.to_thread(self.supervisor.start)
32
+ self._poll_task = asyncio.create_task(self._poll_supervisor())
26
33
  await send({"type": "lifespan.startup.complete"})
27
34
  elif message["type"] == "lifespan.shutdown":
35
+ poll_task = self._poll_task
36
+ self._poll_task = None
37
+ if poll_task is not None:
38
+ poll_task.cancel()
39
+ try:
40
+ await poll_task
41
+ except asyncio.CancelledError:
42
+ pass
28
43
  if self.supervisor is not None:
29
44
  await asyncio.to_thread(self.supervisor.stop)
30
45
  self.supervisor = None
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "dj-queue"
7
- version = "0.5.0"
7
+ version = "0.5.1"
8
8
  description = "Database-backed task queue backend for Django’s Tasks framework."
9
9
  readme = "README.md"
10
10
  license = "MIT"
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