dj-queue 0.4.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.4.0 → dj_queue-0.5.1}/PKG-INFO +191 -96
  2. {dj_queue-0.4.0 → dj_queue-0.5.1}/README.md +187 -94
  3. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/admin.py +86 -18
  4. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/api.py +68 -8
  5. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/backend.py +4 -4
  6. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/config.py +28 -4
  7. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/contrib/asgi.py +15 -0
  8. dj_queue-0.5.1/dj_queue/contrib/prometheus.py +129 -0
  9. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/dashboard.py +44 -430
  10. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/management/commands/dj_queue_health.py +8 -1
  11. dj_queue-0.5.1/dj_queue/migrations/0005_remove_recurringexecution_dj_queue_recurring_executions_task_key_run_at_unique_and_more.py +100 -0
  12. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/models/jobs.py +11 -11
  13. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/models/recurring.py +13 -4
  14. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/models/runtime.py +11 -1
  15. dj_queue-0.5.1/dj_queue/observability.py +476 -0
  16. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/operations/cleanup.py +11 -2
  17. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/operations/concurrency.py +33 -11
  18. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/operations/jobs.py +99 -39
  19. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/operations/recurring.py +12 -3
  20. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/runtime/base.py +5 -1
  21. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/runtime/scheduler.py +3 -1
  22. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/runtime/supervisor.py +20 -5
  23. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/templates/admin/dj_queue/_dashboard_process_rows.html +1 -0
  24. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/templates/admin/dj_queue/change_form.html +11 -0
  25. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/templates/admin/dj_queue/dashboard.html +4 -48
  26. dj_queue-0.5.1/dj_queue/urls.py +16 -0
  27. dj_queue-0.5.1/dj_queue/views.py +38 -0
  28. {dj_queue-0.4.0 → dj_queue-0.5.1}/pyproject.toml +4 -2
  29. {dj_queue-0.4.0 → dj_queue-0.5.1}/LICENSE +0 -0
  30. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/__init__.py +0 -0
  31. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/apps.py +0 -0
  32. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/contrib/__init__.py +0 -0
  33. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/contrib/gunicorn.py +0 -0
  34. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/db.py +0 -0
  35. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/exceptions.py +0 -0
  36. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/hooks.py +0 -0
  37. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/log.py +0 -0
  38. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/management/__init__.py +0 -0
  39. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/management/commands/__init__.py +0 -0
  40. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/management/commands/dj_queue.py +0 -0
  41. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/management/commands/dj_queue_prune.py +0 -0
  42. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/migrations/0001_initial.py +0 -0
  43. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/migrations/0002_pause_semaphore.py +0 -0
  44. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/migrations/0003_recurringtask_recurringexecution.py +0 -0
  45. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/migrations/0004_dashboard.py +0 -0
  46. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/migrations/__init__.py +0 -0
  47. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/models/__init__.py +0 -0
  48. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/operations/__init__.py +0 -0
  49. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/routers.py +0 -0
  50. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/runtime/__init__.py +0 -0
  51. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/runtime/dispatcher.py +0 -0
  52. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/runtime/errors.py +0 -0
  53. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/runtime/interruptible.py +0 -0
  54. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/runtime/notify.py +0 -0
  55. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/runtime/pidfile.py +0 -0
  56. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/runtime/pool.py +0 -0
  57. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/runtime/procline.py +0 -0
  58. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/runtime/worker.py +0 -0
  59. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/templates/admin/dj_queue/_dashboard_recurring_rows.html +0 -0
  60. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/templates/admin/dj_queue/_dashboard_section_table.html +0 -0
  61. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/templates/admin/dj_queue/_dashboard_semaphore_rows.html +0 -0
  62. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/templates/admin/dj_queue/_paginator.html +0 -0
  63. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/templates/admin/dj_queue/_queue_controls.html +0 -0
  64. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/templates/admin/dj_queue/_sortable_header_cells.html +0 -0
  65. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/templates/admin/dj_queue/change_list.html +0 -0
  66. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/templates/admin/dj_queue/includes/fieldset.html +0 -0
  67. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/templates/admin/dj_queue/queue_jobs.html +0 -0
  68. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/templatetags/__init__.py +0 -0
  69. {dj_queue-0.4.0 → dj_queue-0.5.1}/dj_queue/templatetags/dj_queue_admin.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dj-queue
3
- Version: 0.4.0
4
- Summary: Database-backed task queue backend for Django's django.tasks framework
3
+ Version: 0.5.1
4
+ Summary: Database-backed task queue backend for Djangos Tasks framework.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
7
7
  Classifier: Development Status :: 4 - Beta
@@ -17,11 +17,13 @@ Requires-Dist: croniter>=6.2.2
17
17
  Requires-Dist: django>=6.0.0
18
18
  Requires-Dist: pyyaml>=6.0.3
19
19
  Requires-Dist: psycopg>=3.3.3 ; extra == 'postgres'
20
+ Requires-Dist: prometheus-client>=0.4.0 ; extra == 'prometheus'
20
21
  Requires-Python: >=3.12
21
22
  Project-URL: Homepage, https://github.com/coriocactus/dj_queue
22
23
  Project-URL: Repository, https://github.com/coriocactus/dj_queue
23
24
  Project-URL: Issues, https://github.com/coriocactus/dj_queue/issues
24
25
  Provides-Extra: postgres
26
+ Provides-Extra: prometheus
25
27
  Description-Content-Type: text/markdown
26
28
 
27
29
  # dj_queue
@@ -43,7 +45,7 @@ It keeps the queue, live execution state, runtime metadata, and task results in
43
45
  - immediate, scheduled, recurring, and concurrency-limited work
44
46
 
45
47
  `dj_queue` is inspired by Rails' [Solid Queue](https://github.com/rails/solid_queue),
46
- but shaped to fit Django's [task backend API](https://docs.djangoproject.com/en/6.0/topics/tasks/).
48
+ shaped to fit Django's [task backend API](https://docs.djangoproject.com/en/6.0/topics/tasks/).
47
49
 
48
50
  ## Why dj_queue
49
51
 
@@ -71,20 +73,13 @@ Install the package:
71
73
  pip install dj-queue
72
74
  ```
73
75
 
74
- Backend-specific extras are available when you want `dj_queue` to install a
75
- database adapter for you:
76
+ Optional extras:
76
77
 
77
78
  ```bash
78
- pip install "dj-queue[postgres]"
79
+ pip install "dj-queue[postgres]" # psycopg for PostgreSQL + LISTEN/NOTIFY
80
+ pip install "dj-queue[prometheus]" # prometheus_client for /dj_queue/metrics
79
81
  ```
80
82
 
81
- Notes:
82
-
83
- - `postgres` installs `psycopg`, which Django's PostgreSQL backend and
84
- `dj_queue`'s optional `LISTEN/NOTIFY` wakeups use
85
- - for MySQL or MariaDB, install and configure a Django-compatible driver in
86
- your application following Django's database docs
87
-
88
83
  Add `dj_queue` to `INSTALLED_APPS`, register the router, and point Django's task
89
84
  backend at `DjQueueBackend`:
90
85
 
@@ -164,12 +159,10 @@ If Django admin is installed, `dj_queue` adds an operator dashboard at
164
159
  - queue, process, recurring-task, and semaphore overview
165
160
  - backend-aware dashboard and raw changelists
166
161
  - queue controls: pause, resume, clear ready
167
- - job detail action: enqueue a fresh copy of any stored job
168
- - recurring-task actions: unschedule from list and detail views
169
- - pause detail action: resume the paused queue from the raw pause row
170
- - failed-job actions: retry and discard from list and detail views
162
+ - job actions: enqueue a fresh copy of any stored job
163
+ - failed jobs: retry and discard from list and detail views
164
+ - unschedule dynamic recurring tasks
171
165
  - queue drill-down pages for state-specific inspection
172
- - queue drill-down actions: discard ready, scheduled, and blocked jobs; retry or discard failed jobs; enqueue finished jobs again
173
166
 
174
167
  **Dashboard overview**
175
168
 
@@ -328,54 +321,30 @@ Runners heartbeat into the queue database. If claimed work is left behind,
328
321
  Use `python manage.py dj_queue_health` to check whether any fresh runtime
329
322
  process rows exist for a backend.
330
323
 
331
- ## Database Support
332
-
333
- | Backend | Support level | Notes |
334
- |---|---|---|
335
- | PostgreSQL | first-class | polling, `SKIP LOCKED`, and optional `LISTEN/NOTIFY` |
336
- | MySQL 8+ | supported | polling plus `SKIP LOCKED` |
337
- | MariaDB 10.6+ | supported | polling plus `SKIP LOCKED` |
338
- | SQLite | supported with limits | polling only, serialized writes, no `SKIP LOCKED`, no `LISTEN/NOTIFY`; practical for development, CI, and smaller deployments |
339
-
340
- Polling is the portability path everywhere. Backend-specific features improve
341
- latency and throughput but are not correctness requirements.
342
-
343
- ## Data Contract
324
+ ### Data Contract
344
325
 
345
- Job payloads and persisted return values are stored in JSON columns, so they
346
- must be JSON round-trippable.
326
+ Job payloads and persisted return values are stored in JSON columns, so they must be JSON round-trippable.
347
327
 
348
328
  - enqueueing args or kwargs that cannot round-trip through JSON fails immediately
349
329
  - returning a non-JSON-serializable value marks the job failed instead of
350
330
  leaving it claimed forever
351
331
 
352
- If you need to pass model instances, files, or custom objects, store them
353
- elsewhere and pass identifiers or serialized data instead.
354
-
355
- ## Errors When Enqueuing
356
-
357
- `DjQueueBackend.enqueue()` raises `dj_queue.exceptions.EnqueueError` for
358
- backend-side validation failures instead of silently dropping work.
332
+ If you need to pass model instances, files, or custom objects, store them elsewhere and pass identifiers or serialized data instead.
359
333
 
360
- Common reasons include:
334
+ ## Database Support
361
335
 
362
- - args or kwargs are not JSON round-trippable
363
- - `concurrency_key` is set without `concurrency_limit`
364
- - `concurrency_key` cannot be resolved from the enqueue arguments
365
- - `concurrency_key` does not resolve to a non-empty string up to 255 chars
366
- - `on_conflict` is not `"block"` or `"discard"`
336
+ | Backend | Support level | Notes |
337
+ |---|---|---|
338
+ | PostgreSQL | first-class | polling, `SKIP LOCKED`, and optional `LISTEN/NOTIFY` |
339
+ | MySQL 8+ | supported | polling plus `SKIP LOCKED` |
340
+ | MariaDB 10.6+ | supported | polling plus `SKIP LOCKED` |
341
+ | SQLite | supported with limits | polling only, serialized writes, no `SKIP LOCKED`, no `LISTEN/NOTIFY`; practical for development, CI, and smaller deployments |
367
342
 
368
- ```python
369
- from dj_queue.exceptions import EnqueueError
343
+ For MySQL or MariaDB, install and configure a Django-compatible driver following Django's database docs.
370
344
 
371
- try:
372
- sync_account.enqueue(account_id, "refresh")
373
- except EnqueueError as exc:
374
- handle_enqueue_error(exc)
375
- ```
345
+ Polling is the portability path everywhere. Backend-specific features improve latency and throughput but are not correctness requirements.
376
346
 
377
- Task execution errors are different: they become failed jobs and stay
378
- inspectable in the queue database.
347
+ For production PostgreSQL operational guidance, see [Postgres Queue Health](#postgres-queue-health).
379
348
 
380
349
  ## Recurring Tasks
381
350
 
@@ -432,9 +401,10 @@ config.
432
401
  The scheduler is part of the normal `dj_queue` runtime. You do not run a
433
402
  separate recurring service.
434
403
 
435
- Recurring notes:
404
+ Notes:
436
405
 
437
406
  - schedules are cron expressions
407
+ - recurring task keys are scoped per backend alias
438
408
  - only dynamic tasks can be unscheduled at runtime; unscheduling a static task returns `0`
439
409
  - Django admin exposes the same unschedule operation on recurring-task list and detail views
440
410
  - multiple schedulers sharing the same recurring config dedupe firing in the database
@@ -468,6 +438,10 @@ With this configuration:
468
438
  - later jobs for the same key can block until capacity is released
469
439
  - `on_conflict = "discard"` turns the same pattern into singleton-style work
470
440
 
441
+ Semaphore rows remain shared on the queue database. If you want per-backend
442
+ isolation for a limit, express that in the `concurrency_key` itself rather than
443
+ expecting one semaphore namespace per backend alias.
444
+
471
445
  ## Queue Operations
472
446
 
473
447
  `QueueInfo` exposes operational queue controls without bypassing the queue
@@ -490,6 +464,7 @@ orders.clear()
490
464
  Queue control notes:
491
465
 
492
466
  - pausing a queue stops future claims, not enqueueing or already-claimed work
467
+ - pause rows are scoped per backend alias
493
468
  - `clear()` discards ready jobs only
494
469
  - pass `backend_alias=` when you want to target a non-default `TASKS` alias
495
470
 
@@ -549,6 +524,66 @@ except UndiscardableError:
549
524
 
550
525
  Failures stay inspectable until you act on them.
551
526
 
527
+ ## Errors When Enqueuing
528
+
529
+ `DjQueueBackend.enqueue()` raises `dj_queue.exceptions.EnqueueError` for
530
+ backend-side validation failures instead of silently dropping work.
531
+
532
+ Common reasons include:
533
+
534
+ - args or kwargs are not JSON round-trippable
535
+ - `concurrency_key` is set without `concurrency_limit`
536
+ - `concurrency_key` cannot be resolved from the enqueue arguments
537
+ - `concurrency_key` does not resolve to a non-empty string up to 255 chars
538
+ - `on_conflict` is not `"block"` or `"discard"`
539
+
540
+ ```python
541
+ from dj_queue.exceptions import EnqueueError
542
+
543
+ try:
544
+ sync_account.enqueue(account_id, "refresh")
545
+ except EnqueueError as exc:
546
+ handle_enqueue_error(exc)
547
+ ```
548
+
549
+ Task execution errors are different: they become failed jobs and stay
550
+ inspectable in the queue database.
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.
@@ -591,6 +626,28 @@ python manage.py migrate dj_queue --database queue
591
626
  With this setup, `dj_queue`'s ORM queries and raw SQL helpers stay on the queue
592
627
  database.
593
628
 
629
+ ## Postgres Queue Health
630
+
631
+ Operational and configuration guidance for scaling with `dj_queue` in
632
+ production PostgreSQL deployments, covering dedicated database setup, retention
633
+ policy, and autovacuum tuning.
634
+
635
+ - Use a dedicated queue database via `database_alias`. Keep reporting and
636
+ long-running transactions off the queue database.
637
+ - Keep retention short. Set `preserve_finished_jobs = False` if you do not need
638
+ successful results. Otherwise use bounded `clear_finished_jobs_after`,
639
+ `clear_failed_jobs_after`, and `clear_recurring_executions_after` values.
640
+ - Run `python manage.py dj_queue_prune` regularly for stricter cleanup.
641
+ - Keep `use_skip_locked = True` and `listen_notify = True` unless you have a
642
+ specific reason not to.
643
+ - Tune autovacuum for `dj_queue_jobs` and the high-churn
644
+ `dj_queue_*_executions` tables, often default OLTP settings are too
645
+ conservative for queue workloads.
646
+ - Keep transactions short across workers and the rest of your app. Long-lived
647
+ transactions pin dead tuples and delay vacuum.
648
+ - Monitor dead tuples, autovacuum frequency, and long-running queries before
649
+ reaching for partitioning or bulk-ingest paths.
650
+
594
651
  ## Embedded Server Mode
595
652
 
596
653
  `dj_queue` can run inside an existing server process via embedded async
@@ -622,6 +679,21 @@ signal handling to the host server.
622
679
 
623
680
  ## Configuration
624
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
+
625
697
  ### Deployment topology
626
698
 
627
699
  Once migrations are in place, start processing jobs with `python manage.py dj_queue`
@@ -698,9 +770,13 @@ python manage.py dj_queue --config /etc/dj_queue.yml
698
770
  DJ_QUEUE_CONFIG=/etc/dj_queue.yml python manage.py dj_queue
699
771
  ```
700
772
 
701
- The YAML file should contain a single mapping of backend option values. It uses
702
- the same shape as `TASKS[backend_alias]["OPTIONS"]`, not the full Django
703
- `TASKS` structure:
773
+ The YAML file is an overlay on `TASKS[backend_alias]["OPTIONS"]`. It supports
774
+ two shapes:
775
+
776
+ - a flat mapping of option values for the selected backend alias
777
+ - a `backends` mapping keyed by backend alias, where only the selected alias is applied
778
+
779
+ Flat mapping example:
704
780
 
705
781
  ```yaml
706
782
  mode: async
@@ -737,8 +813,28 @@ recurring:
737
813
  description: nightly cleanup
738
814
  ```
739
815
 
740
- This file is merged on top of `TASKS[backend_alias]["OPTIONS"]`, then any
741
- environment-variable and CLI overrides win after that.
816
+ Multi-backend overlay example:
817
+
818
+ ```yaml
819
+ backends:
820
+ default:
821
+ mode: async
822
+ database_alias: default
823
+ workers:
824
+ - queues: ["default", "email*"]
825
+ threads: 8
826
+ processes: 1
827
+ polling_interval: 0.1
828
+
829
+ critical:
830
+ mode: fork
831
+ database_alias: queue
832
+ workers:
833
+ - queues: ["alerts", "critical-review"]
834
+ threads: 2
835
+ processes: 1
836
+ polling_interval: 0.05
837
+ ```
742
838
 
743
839
  Environment overrides currently supported by `dj_queue` itself:
744
840
 
@@ -746,41 +842,6 @@ Environment overrides currently supported by `dj_queue` itself:
746
842
  - `DJ_QUEUE_MODE`
747
843
  - `DJ_QUEUE_SKIP_RECURRING`
748
844
 
749
- ## Lifecycle Hooks
750
-
751
- Register hooks before starting the runtime, typically during Django startup.
752
- Each callback receives the live supervisor or runner instance.
753
-
754
- ```python
755
- from dj_queue.hooks import on_start, on_worker_start, register_hook
756
-
757
- @on_start
758
- def supervisor_started(process):
759
- print(process.name)
760
-
761
- @on_worker_start
762
- def worker_started(process):
763
- print(process.metadata)
764
-
765
- @register_hook("scheduler.exit")
766
- def scheduler_exited(process):
767
- print(process.name)
768
- ```
769
-
770
- Available hook helpers:
771
-
772
- - supervisor: `on_start`, `on_stop`, `on_exit`
773
- - worker: `on_worker_start`, `on_worker_stop`, `on_worker_exit`
774
- - dispatcher: `on_dispatcher_start`, `on_dispatcher_stop`, `on_dispatcher_exit`
775
- - scheduler: `on_scheduler_start`, `on_scheduler_stop`, `on_scheduler_exit`
776
- - generic events: `register_hook("worker.start")`, `register_hook("dispatcher.stop")`, and so on
777
-
778
- Hook notes:
779
-
780
- - hooks fire in registration order
781
- - hook failures do not block later hooks
782
- - hook failures are isolated and routed through `on_thread_error`
783
-
784
845
  ### Runtime infrastructure errors
785
846
 
786
847
  Set `on_thread_error` to a dotted callable path when you want custom handling
@@ -803,6 +864,40 @@ such as hook failures, heartbeat failures, notify-watcher failures, and managed
803
864
  runner crashes. It is not used for exceptions raised by your task code; those
804
865
  become failed jobs instead.
805
866
 
867
+ ## Monitoring
868
+
869
+ Queue statistics are available in JSON via `/dj_queue/stats.json` and in
870
+ Prometheus text format via `/dj_queue/metrics`.
871
+
872
+ Include `dj_queue.urls` to expose them:
873
+
874
+ ```python
875
+ urlpatterns += [path("dj_queue/", include("dj_queue.urls"))]
876
+ ```
877
+
878
+ The `/dj_queue/metrics` endpoint requires the `prometheus` extra:
879
+
880
+ ```bash
881
+ pip install "dj-queue[prometheus]"
882
+ ```
883
+
884
+ Exported metric families:
885
+
886
+ - `dj_queue_queue_jobs{backend,queue,state}`
887
+ - `dj_queue_queue_paused{backend,queue}`
888
+ - `dj_queue_queue_latency_seconds{backend,queue}`
889
+ - `dj_queue_queue_live_workers{backend,queue}`
890
+ - `dj_queue_runner_processes{backend,status}`
891
+ - `dj_queue_runner_processes_by_kind{backend,kind,status}`
892
+ - `dj_queue_recurring_tasks{backend}`
893
+ - `dj_queue_semaphores{queue_database}`
894
+ - `dj_queue_process_rows{backend}`
895
+
896
+ Both endpoints support bearer token authentication. Set
897
+ `DJ_QUEUE_OBSERVABILITY_TOKEN` in `settings.py` and include it as
898
+ `Authorization: Bearer <token>`. Leave it unset if you protect these URLs at
899
+ the network or proxy layer.
900
+
806
901
  ## License
807
902
 
808
903
  MIT