dj-queue 0.14.2__tar.gz → 0.15.0__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 (119) hide show
  1. {dj_queue-0.14.2 → dj_queue-0.15.0}/PKG-INFO +4 -4
  2. {dj_queue-0.14.2 → dj_queue-0.15.0}/README.md +3 -3
  3. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/config.py +29 -10
  4. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/contrib/asgi.py +2 -7
  5. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/contrib/gunicorn.py +6 -8
  6. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/dashboard.py +5 -11
  7. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/dashboard_tables.py +2 -2
  8. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/db.py +3 -3
  9. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/hooks.py +2 -7
  10. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/log.py +2 -12
  11. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/management/commands/dj_queue_prune.py +5 -3
  12. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/metrics.py +2 -2
  13. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/operations/_helpers.py +1 -38
  14. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/operations/claiming.py +19 -35
  15. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/operations/cleanup.py +10 -14
  16. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/operations/concurrency.py +58 -131
  17. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/operations/dispatch.py +8 -9
  18. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/operations/enqueue.py +25 -46
  19. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/operations/execution.py +23 -49
  20. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/operations/jobs.py +64 -196
  21. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/operations/queues.py +3 -3
  22. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/operations/recovery.py +26 -66
  23. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/operations/recurring.py +29 -49
  24. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/queue_state.py +14 -84
  25. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/reads/queues.py +13 -2
  26. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/runtime/base.py +17 -64
  27. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/runtime/connection_budget.py +1 -2
  28. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/runtime/dispatcher.py +2 -33
  29. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/runtime/errors.py +4 -6
  30. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/runtime/notify.py +15 -34
  31. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/runtime/pidfile.py +0 -7
  32. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/runtime/pool.py +1 -12
  33. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/runtime/scheduler.py +9 -75
  34. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/runtime/supervisor.py +47 -180
  35. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/runtime/worker.py +3 -38
  36. dj_queue-0.15.0/dj_queue/sql/common.py +114 -0
  37. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/templates/admin/dj_queue/dashboard.html +0 -1
  38. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/templates/admin/dj_queue/queue_jobs.html +2 -2
  39. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/wakeup.py +1 -4
  40. dj_queue-0.15.0/pyproject.toml +92 -0
  41. dj_queue-0.14.2/pyproject.toml → dj_queue-0.15.0/pyproject.toml.orig +2 -2
  42. dj_queue-0.14.2/dj_queue/runtime/procline.py +0 -11
  43. dj_queue-0.14.2/dj_queue/sql/common.py +0 -220
  44. {dj_queue-0.14.2 → dj_queue-0.15.0}/LICENSE +0 -0
  45. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/__init__.py +0 -0
  46. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/admin.py +0 -0
  47. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/admin_permissions.py +0 -0
  48. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/api.py +0 -0
  49. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/apps.py +0 -0
  50. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/backend.py +0 -0
  51. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/contrib/__init__.py +0 -0
  52. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/contrib/prometheus.py +0 -0
  53. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/cron.py +0 -0
  54. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/dashboard_actions.py +0 -0
  55. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/exceptions.py +0 -0
  56. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/health.py +0 -0
  57. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/management/__init__.py +0 -0
  58. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/management/commands/__init__.py +0 -0
  59. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/management/commands/dj_queue.py +0 -0
  60. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/management/commands/dj_queue_health.py +0 -0
  61. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/management/commands/dj_queue_postgres_autovacuum.py +0 -0
  62. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/migrations/0001_initial.py +0 -0
  63. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/migrations/0002_pause_semaphore.py +0 -0
  64. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/migrations/0003_recurringtask_recurringexecution.py +0 -0
  65. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/migrations/0004_dashboard.py +0 -0
  66. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/migrations/0005_remove_recurringexecution_dj_queue_recurring_executions_task_key_run_at_unique_and_more.py +0 -0
  67. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/migrations/0006_blockedexecution_dj_queue_bl_concurr_2d8393_idx_and_more.py +0 -0
  68. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/migrations/0007_recurringtask_next_run_at.py +0 -0
  69. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/migrations/0008_remove_blockedexecution_dj_queue_bl_concurr_1ce730_idx_and_more.py +0 -0
  70. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/migrations/0009_remove_process_dj_queue_processes_name_supervisor_unique_and_more.py +0 -0
  71. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/migrations/0010_remove_process_djq_pr_name_parent_uniq_and_more.py +0 -0
  72. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/migrations/0011_remove_blockedexecution_djq_bl_b_conc_idx_and_more.py +0 -0
  73. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/migrations/0012_job_djq_jobs_b_queue_id_idx_job_djq_jobs_b_conc_idx_and_more.py +0 -0
  74. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/migrations/0013_failedexecution_retry_at_and_more.py +0 -0
  75. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/migrations/0014_semaphore_active_count.py +0 -0
  76. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/migrations/0015_job_concurrency_duration.py +0 -0
  77. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/migrations/0016_job_concurrency_limit.py +0 -0
  78. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/migrations/0017_job_concurrency_on_conflict.py +0 -0
  79. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/migrations/0018_recurringexecution_intended_job_id.py +0 -0
  80. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/migrations/__init__.py +0 -0
  81. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/models/__init__.py +0 -0
  82. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/models/jobs.py +0 -0
  83. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/models/recurring.py +0 -0
  84. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/models/runtime.py +0 -0
  85. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/observability.py +0 -0
  86. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/operations/__init__.py +0 -0
  87. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/operations/_insert.py +0 -0
  88. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/postgres_diagnostics.py +0 -0
  89. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/py.typed +0 -0
  90. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/queue_selectors.py +0 -0
  91. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/reads/__init__.py +0 -0
  92. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/reads/controls.py +0 -0
  93. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/reads/jobs.py +0 -0
  94. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/reads/processes.py +0 -0
  95. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/routers.py +0 -0
  96. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/runtime/__init__.py +0 -0
  97. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/runtime/interruptible.py +0 -0
  98. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/runtime/topology.py +0 -0
  99. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/sql/__init__.py +0 -0
  100. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/sql/mysql.py +0 -0
  101. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/sql/postgres.py +0 -0
  102. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/sql/sqlite.py +0 -0
  103. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/sql/state.py +0 -0
  104. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/static/admin/dj_queue/breadcrumbs.css +0 -0
  105. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/task_results.py +0 -0
  106. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/templates/admin/dj_queue/_dashboard_process_rows.html +0 -0
  107. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/templates/admin/dj_queue/_dashboard_recurring_rows.html +0 -0
  108. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/templates/admin/dj_queue/_dashboard_section_table.html +0 -0
  109. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/templates/admin/dj_queue/_dashboard_semaphore_rows.html +0 -0
  110. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/templates/admin/dj_queue/_paginator.html +0 -0
  111. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/templates/admin/dj_queue/_queue_controls.html +0 -0
  112. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/templates/admin/dj_queue/_sortable_header_cells.html +0 -0
  113. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/templates/admin/dj_queue/change_form.html +0 -0
  114. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/templates/admin/dj_queue/change_list.html +0 -0
  115. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/templates/admin/dj_queue/includes/fieldset.html +0 -0
  116. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/templatetags/__init__.py +0 -0
  117. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/templatetags/dj_queue_admin.py +0 -0
  118. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/urls.py +0 -0
  119. {dj_queue-0.14.2 → dj_queue-0.15.0}/dj_queue/views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dj-queue
3
- Version: 0.14.2
3
+ Version: 0.15.0
4
4
  Summary: Database-backed task queue backend for Django’s Tasks framework.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -369,7 +369,7 @@ The database floor follows the selected Django release:
369
369
  | 6.1 | 15+ | 8.4+ | 10.11+ | 3.37+ |
370
370
 
371
371
  CI keeps the floors on Django 6.0 and tests newer database lines through
372
- PostgreSQL 18, MySQL 9.7 and 26.7, and MariaDB 12.3. Django 6.1 only runs with
372
+ PostgreSQL 18, MySQL 9.7 and 26.10, and MariaDB 12.3 and 13.0. Django 6.1 only runs with
373
373
  database versions that it supports.
374
374
 
375
375
  For MySQL or MariaDB, install and configure a Django-compatible driver following Django's database docs.
@@ -939,13 +939,14 @@ Global options:
939
939
  | `default_concurrency_duration` | `180` | default semaphore TTL in seconds |
940
940
  | `use_skip_locked` | `true` | use `SKIP LOCKED` when the active backend supports it |
941
941
  | `listen_notify` | `true` | PostgreSQL-only worker wakeup optimization layered on top of polling |
942
- | `silence_polling` | `true` | suppress `dj_queue`'s own poll-cycle noise without mutating Django's global SQL logger |
943
942
  | `process_heartbeat_interval` | `60` | seconds between process heartbeat writes |
944
943
  | `process_alive_threshold` | `300` | seconds before a process row is stale |
945
944
  | `shutdown_timeout` | `5` | graceful drain window before standalone shutdown gives up on waiting |
946
945
  | `supervisor_pidfile` | `null` | optional pidfile guard for standalone supervisors |
947
946
  | `on_thread_error` | `null` | dotted callback path for runtime infrastructure exceptions |
948
947
 
948
+ `silence_polling` is deprecated: it never had an effect, still loads with a `FutureWarning`, and will be rejected in a future release, so remove it from settings and TOML overlays.
949
+
949
950
  Worker entry options:
950
951
 
951
952
  | Option | Default | Meaning |
@@ -1027,7 +1028,6 @@ database_alias = "queue"
1027
1028
  preserve_finished_jobs = true
1028
1029
  clear_finished_jobs_after = 86400
1029
1030
  listen_notify = true
1030
- silence_polling = true
1031
1031
 
1032
1032
  [[workers]]
1033
1033
  queues = ["default", "email*"]
@@ -342,7 +342,7 @@ The database floor follows the selected Django release:
342
342
  | 6.1 | 15+ | 8.4+ | 10.11+ | 3.37+ |
343
343
 
344
344
  CI keeps the floors on Django 6.0 and tests newer database lines through
345
- PostgreSQL 18, MySQL 9.7 and 26.7, and MariaDB 12.3. Django 6.1 only runs with
345
+ PostgreSQL 18, MySQL 9.7 and 26.10, and MariaDB 12.3 and 13.0. Django 6.1 only runs with
346
346
  database versions that it supports.
347
347
 
348
348
  For MySQL or MariaDB, install and configure a Django-compatible driver following Django's database docs.
@@ -912,13 +912,14 @@ Global options:
912
912
  | `default_concurrency_duration` | `180` | default semaphore TTL in seconds |
913
913
  | `use_skip_locked` | `true` | use `SKIP LOCKED` when the active backend supports it |
914
914
  | `listen_notify` | `true` | PostgreSQL-only worker wakeup optimization layered on top of polling |
915
- | `silence_polling` | `true` | suppress `dj_queue`'s own poll-cycle noise without mutating Django's global SQL logger |
916
915
  | `process_heartbeat_interval` | `60` | seconds between process heartbeat writes |
917
916
  | `process_alive_threshold` | `300` | seconds before a process row is stale |
918
917
  | `shutdown_timeout` | `5` | graceful drain window before standalone shutdown gives up on waiting |
919
918
  | `supervisor_pidfile` | `null` | optional pidfile guard for standalone supervisors |
920
919
  | `on_thread_error` | `null` | dotted callback path for runtime infrastructure exceptions |
921
920
 
921
+ `silence_polling` is deprecated: it never had an effect, still loads with a `FutureWarning`, and will be rejected in a future release, so remove it from settings and TOML overlays.
922
+
922
923
  Worker entry options:
923
924
 
924
925
  | Option | Default | Meaning |
@@ -1000,7 +1001,6 @@ database_alias = "queue"
1000
1001
  preserve_finished_jobs = true
1001
1002
  clear_finished_jobs_after = 86400
1002
1003
  listen_notify = true
1003
- silence_polling = true
1004
1004
 
1005
1005
  [[workers]]
1006
1006
  queues = ["default", "email*"]
@@ -62,12 +62,13 @@ DEFAULT_OPTIONS = {
62
62
  "database_alias": "default",
63
63
  "use_skip_locked": True,
64
64
  "listen_notify": True,
65
- "silence_polling": True,
66
65
  "async_thread_sensitive": False,
67
66
  "async_close_connections": False,
68
67
  "on_thread_error": None,
69
68
  }
70
69
 
70
+ DEPRECATED_OPTIONS = ("silence_polling",)
71
+
71
72
  DJ_QUEUE_BACKEND_PATH = "dj_queue.backend.DjQueueBackend"
72
73
 
73
74
  TRUTHY_ENV_VALUES = {"1", "true", "yes", "on"}
@@ -138,7 +139,6 @@ class BackendConfig(ConfigValue):
138
139
  database_alias: str = "default"
139
140
  use_skip_locked: bool = True
140
141
  listen_notify: bool = True
141
- silence_polling: bool = True
142
142
  async_thread_sensitive: bool = False
143
143
  async_close_connections: bool = False
144
144
  on_thread_error: str | None = None
@@ -150,6 +150,14 @@ class BackendConfig(ConfigValue):
150
150
  def has_scheduler_work(self) -> bool:
151
151
  return self.scheduler is not None
152
152
 
153
+ @property
154
+ def cleanup_enabled(self) -> bool:
155
+ return (
156
+ (self.preserve_finished_jobs and self.clear_finished_jobs_after is not None)
157
+ or self.clear_failed_jobs_after is not None
158
+ or self.clear_recurring_executions_after is not None
159
+ )
160
+
153
161
 
154
162
  def resolve_backend_config(
155
163
  backend_alias: str = "default", config: BackendConfig | None = None
@@ -293,7 +301,6 @@ def _build_backend_config(
293
301
  database_alias=_string_option(resolved_options["database_alias"], "database_alias"),
294
302
  use_skip_locked=_bool_option(resolved_options["use_skip_locked"], "use_skip_locked"),
295
303
  listen_notify=_bool_option(resolved_options["listen_notify"], "listen_notify"),
296
- silence_polling=_bool_option(resolved_options["silence_polling"], "silence_polling"),
297
304
  async_thread_sensitive=_bool_option(
298
305
  resolved_options["async_thread_sensitive"], "async_thread_sensitive"
299
306
  ),
@@ -389,6 +396,7 @@ def _resolved_options(
389
396
  settings_options = backend_block.get("OPTIONS", {})
390
397
  if not isinstance(settings_options, Mapping):
391
398
  raise ImproperlyConfigured("TASKS backend OPTIONS must be a mapping")
399
+ settings_options = _without_deprecated_options(settings_options, "TASKS backend OPTIONS")
392
400
  _reject_unknown_options(settings_options, DEFAULT_OPTIONS, "TASKS backend OPTIONS")
393
401
 
394
402
  resolved_options = dict(DEFAULT_OPTIONS)
@@ -444,11 +452,27 @@ def _load_toml_options(config_path: Any, *, backend_alias: str) -> dict[str, Any
444
452
 
445
453
 
446
454
  def _validated_toml_options(options: Mapping[str, Any], setting_name: str) -> dict[str, Any]:
447
- options = _json_serializable_options(options, setting_name)
455
+ options = _without_deprecated_options(
456
+ _json_serializable_options(options, setting_name), setting_name
457
+ )
448
458
  _reject_unknown_options(options, DEFAULT_OPTIONS, setting_name)
449
459
  return options
450
460
 
451
461
 
462
+ def _without_deprecated_options(options: Mapping[str, Any], setting_name: str) -> dict[str, Any]:
463
+ options = dict(options)
464
+ for name in DEPRECATED_OPTIONS:
465
+ if name in options:
466
+ del options[name]
467
+ warnings.warn(
468
+ f"dj_queue {setting_name} option {name!r} has no effect and will be rejected in a "
469
+ "future release; remove it",
470
+ FutureWarning,
471
+ stacklevel=2,
472
+ )
473
+ return options
474
+
475
+
452
476
  def _json_serializable_options(options: Mapping[str, Any], setting_name: str) -> dict[str, Any]:
453
477
  try:
454
478
  json.dumps(options, sort_keys=True, separators=(",", ":"), allow_nan=False)
@@ -669,12 +693,7 @@ def _build_recurring_config(
669
693
 
670
694
 
671
695
  def _scheduler_has_work(config: BackendConfig) -> bool:
672
- has_cleanup = (
673
- (config.preserve_finished_jobs and config.clear_finished_jobs_after is not None)
674
- or config.clear_failed_jobs_after is not None
675
- or config.clear_recurring_executions_after is not None
676
- )
677
- return config.scheduler.dynamic_tasks_enabled or bool(config.recurring) or has_cleanup
696
+ return config.scheduler.dynamic_tasks_enabled or bool(config.recurring) or config.cleanup_enabled
678
697
 
679
698
 
680
699
  def _as_queue_selectors(value: Any) -> tuple[str, ...]:
@@ -24,14 +24,9 @@ class DjQueueLifespan:
24
24
  self.supervisor is not None and self._poll_stop is not None and not self._poll_stop.is_set()
25
25
  ):
26
26
  try:
27
- poll_once = getattr(self.supervisor, "poll_once_if_running", self.supervisor.poll_once)
28
- keep_polling = await asyncio.to_thread(poll_once)
27
+ keep_polling = await asyncio.to_thread(self.supervisor.poll_once_if_running)
29
28
  except Exception as error:
30
- handle_thread_error(
31
- error,
32
- context="supervisor.run",
33
- backend_alias=self.supervisor.backend_alias,
34
- )
29
+ handle_thread_error(error, context="supervisor.run", config=self.supervisor.backend_config)
35
30
  keep_polling = True
36
31
 
37
32
  if keep_polling is False:
@@ -4,6 +4,7 @@ import tempfile
4
4
  import threading
5
5
  from pathlib import Path
6
6
 
7
+ from dj_queue.config import load_backend_config
7
8
  from dj_queue.runtime.errors import handle_thread_error
8
9
 
9
10
  LOCK_PATH_PREFIX = "dj_queue_gunicorn_supervisor"
@@ -68,15 +69,10 @@ def _start_embedded_supervisor(worker, *, backend_alias="default"):
68
69
  stop_event = worker._dj_queue_supervisor_poll_stop
69
70
  while stop_event.wait(supervisor.polling_interval) is False:
70
71
  try:
71
- poll_once = getattr(supervisor, "poll_once_if_running", supervisor.poll_once)
72
- if poll_once() is False:
72
+ if supervisor.poll_once_if_running() is False:
73
73
  return
74
74
  except Exception as error:
75
- handle_thread_error(
76
- error,
77
- context="supervisor.run",
78
- backend_alias=supervisor.backend_alias,
79
- )
75
+ handle_thread_error(error, context="supervisor.run", config=supervisor.backend_config)
80
76
 
81
77
  poll_thread = threading.Thread(target=poll_supervisor, daemon=True)
82
78
  worker._dj_queue_supervisor_poll_thread = poll_thread
@@ -94,7 +90,9 @@ def _start_lock_retry_loop(worker, *, backend_alias="default"):
94
90
  try:
95
91
  supervisor = _start_embedded_supervisor(worker, backend_alias=backend_alias)
96
92
  except Exception as error:
97
- handle_thread_error(error, context="gunicorn.supervisor", backend_alias=backend_alias)
93
+ handle_thread_error(
94
+ error, context="gunicorn.supervisor", config=load_backend_config(backend_alias)
95
+ )
98
96
  continue
99
97
  if supervisor is not None:
100
98
  retry_stop.set()
@@ -12,9 +12,9 @@ from dj_queue.dashboard_tables import OVERVIEW_COUNT_LABELS, OVERVIEW_SORTS, QUE
12
12
  from dj_queue.db import database_capabilities, get_database_alias
13
13
  from dj_queue.observability import backend_choices, configured_backend_aliases
14
14
  from dj_queue.queue_state import (
15
- QUEUE_STATE_DEFINITIONS,
16
15
  QUEUE_STATE_LABELS,
17
16
  QUEUE_STATES,
17
+ SUMMARY_STATE_DEFINITIONS,
18
18
  queue_state_count_key,
19
19
  queue_state_queryset,
20
20
  )
@@ -154,13 +154,13 @@ def queue_page_context(*, backend_alias, queue_name, state, page_number, query_p
154
154
  )
155
155
  queue_info = QueueInfo(queue_name, backend_alias=backend_alias)
156
156
  state_counts = {
157
- definition.name: queue_row[definition.count_key] for definition in QUEUE_STATE_DEFINITIONS
157
+ definition.name: queue_row[definition.count_key] for definition in SUMMARY_STATE_DEFINITIONS
158
158
  }
159
159
  state_tabs = [
160
160
  {
161
161
  "name": state_name,
162
162
  "label": label,
163
- "count": state_counts[state_name],
163
+ "count": state_counts.get(state_name),
164
164
  "selected": state_name == state,
165
165
  }
166
166
  for state_name, label in QUEUE_STATES
@@ -239,7 +239,6 @@ def _summary_cards(*, backend_alias, queue_rows, process_rows, recurring_count,
239
239
  scheduled_count = sum(row[queue_state_count_key("scheduled")] for row in queue_rows)
240
240
  failed_count = sum(row[queue_state_count_key("failed")] for row in queue_rows)
241
241
  blocked_count = sum(row[queue_state_count_key("blocked")] for row in queue_rows)
242
- invalid_count = sum(row[queue_state_count_key("invalid")] for row in queue_rows)
243
242
  live_processes = sum(1 for row in process_rows if row["is_live"])
244
243
  stale_processes = len(process_rows) - live_processes
245
244
 
@@ -256,22 +255,17 @@ def _summary_cards(*, backend_alias, queue_rows, process_rows, recurring_count,
256
255
  },
257
256
  {
258
257
  "label": "attention",
259
- "value": failed_count + blocked_count + invalid_count,
258
+ "value": failed_count + blocked_count,
260
259
  "detail_parts": (
261
260
  {
262
261
  "label": f"{failed_count} failed",
263
262
  "url": _job_changelist_url(backend_alias=backend_alias, status="failed"),
264
263
  },
265
- {"label": ","},
264
+ {"label": "and"},
266
265
  {
267
266
  "label": f"{blocked_count} blocked",
268
267
  "url": _job_changelist_url(backend_alias=backend_alias, status="blocked"),
269
268
  },
270
- {"label": "and"},
271
- {
272
- "label": f"{invalid_count} invalid",
273
- "url": _job_changelist_url(backend_alias=backend_alias, status="invalid"),
274
- },
275
269
  ),
276
270
  },
277
271
  {
@@ -7,7 +7,7 @@ from django.core.paginator import Paginator
7
7
  from django.db.models import F
8
8
 
9
9
  from dj_queue.queue_state import (
10
- QUEUE_STATE_DEFINITIONS,
10
+ SUMMARY_STATE_DEFINITIONS,
11
11
  )
12
12
 
13
13
  OVERVIEW_COUNT_LABELS = {
@@ -30,7 +30,7 @@ OVERVIEW_SORTS = {
30
30
  "key": definition.count_key,
31
31
  "default_desc": True,
32
32
  }
33
- for definition in QUEUE_STATE_DEFINITIONS
33
+ for definition in SUMMARY_STATE_DEFINITIONS
34
34
  },
35
35
  "paused": {"label": "paused", "key": "paused", "default_desc": True},
36
36
  "latency": {"label": "latency", "key": "latency_seconds", "default_desc": True},
@@ -8,7 +8,7 @@ from django.core.exceptions import ImproperlyConfigured
8
8
  from django.db import DEFAULT_DB_ALIAS, connections
9
9
  from django.db.utils import OperationalError
10
10
 
11
- from dj_queue.config import resolve_backend_config
11
+ from dj_queue.config import load_backend_config
12
12
 
13
13
  TRANSIENT_DATABASE_RETRY_ATTEMPTS = 3
14
14
  TRANSIENT_DATABASE_RETRY_SLEEP_BASE = 0.01
@@ -45,8 +45,8 @@ class DatabaseCapabilities:
45
45
  uses_serialized_writes: bool
46
46
 
47
47
 
48
- def get_database_alias(backend_alias: str = "default", *, config=None) -> str:
49
- return resolve_backend_config(backend_alias, config).database_alias
48
+ def get_database_alias(backend_alias: str = "default") -> str:
49
+ return load_backend_config(backend_alias).database_alias
50
50
 
51
51
 
52
52
  def locked_queryset(qs, use_skip_locked: bool = True):
@@ -67,17 +67,12 @@ def on_scheduler_exit(fn: Callable[..., Any]):
67
67
  return register_hook("scheduler.exit", fn)
68
68
 
69
69
 
70
- def fire_hooks(event: str, process: Any, *, backend_alias: str = "default", config=None):
70
+ def fire_hooks(event: str, process: Any, *, config):
71
71
  for hook in tuple(_hooks.get(event, ())):
72
72
  try:
73
73
  hook(process)
74
74
  except Exception as error:
75
- handle_thread_error(
76
- error,
77
- context=f"hook:{event}",
78
- backend_alias=backend_alias,
79
- config=config,
80
- )
75
+ handle_thread_error(error, context=f"hook:{event}", config=config)
81
76
 
82
77
 
83
78
  def clear_hooks(event: str | None = None):
@@ -1,19 +1,10 @@
1
1
  import logging
2
2
  from typing import Any
3
3
 
4
- from dj_queue.config import load_backend_config
5
-
6
4
  logger = logging.getLogger("dj_queue")
7
5
 
8
6
 
9
- def event_logging_enabled(
10
- level: int = logging.INFO,
11
- *,
12
- backend_alias: str = "default",
13
- polling: bool = False,
14
- ):
15
- if polling and load_backend_config(backend_alias).silence_polling:
16
- return False
7
+ def event_logging_enabled(level: int = logging.INFO):
17
8
  return logger.isEnabledFor(level)
18
9
 
19
10
 
@@ -22,10 +13,9 @@ def log_event(
22
13
  *,
23
14
  level: int = logging.INFO,
24
15
  backend_alias: str = "default",
25
- polling: bool = False,
26
16
  **fields: Any,
27
17
  ):
28
- if not event_logging_enabled(level, backend_alias=backend_alias, polling=polling):
18
+ if not event_logging_enabled(level):
29
19
  return
30
20
 
31
21
  logger.log(
@@ -1,6 +1,7 @@
1
1
  from django.core.exceptions import ImproperlyConfigured
2
2
  from django.core.management.base import BaseCommand, CommandError
3
3
 
4
+ from dj_queue.config import load_backend_config
4
5
  from dj_queue.operations.cleanup import (
5
6
  clear_failed_jobs,
6
7
  clear_finished_jobs,
@@ -57,23 +58,24 @@ class Command(BaseCommand):
57
58
  raise CommandError("--batch-size must be positive")
58
59
 
59
60
  try:
61
+ config = load_backend_config(options["backend"])
60
62
  finished_deleted = clear_finished_jobs(
61
63
  older_than=options["older_than"],
62
64
  task_path=options["task_path"],
63
65
  batch_size=batch_size,
64
- backend_alias=options["backend"],
66
+ config=config,
65
67
  )
66
68
  failed_deleted = clear_failed_jobs(
67
69
  older_than=options["failed_older_than"],
68
70
  task_path=options["task_path"],
69
71
  batch_size=batch_size,
70
- backend_alias=options["backend"],
72
+ config=config,
71
73
  )
72
74
  recurring_deleted = clear_recurring_executions(
73
75
  older_than=options["recurring_older_than"],
74
76
  task_key=options["task_key"],
75
77
  batch_size=batch_size,
76
- backend_alias=options["backend"],
78
+ config=config,
77
79
  )
78
80
  except ImproperlyConfigured as exc:
79
81
  raise CommandError(str(exc)) from exc
@@ -1,7 +1,7 @@
1
1
  from dataclasses import dataclass
2
2
 
3
3
  from dj_queue import observability
4
- from dj_queue.queue_state import QUEUE_STATE_DEFINITIONS
4
+ from dj_queue.queue_state import SUMMARY_STATE_DEFINITIONS
5
5
 
6
6
 
7
7
  @dataclass(frozen=True, slots=True)
@@ -60,7 +60,7 @@ def _queue_metric_families(snapshots):
60
60
  for snapshot in snapshots:
61
61
  for queue in snapshot.queue_rows:
62
62
  labels = (snapshot.backend_alias, queue["name"])
63
- for definition in QUEUE_STATE_DEFINITIONS:
63
+ for definition in SUMMARY_STATE_DEFINITIONS:
64
64
  jobs.append(
65
65
  MetricSample(labels=(*labels, definition.name), value=queue[definition.count_key])
66
66
  )
@@ -155,29 +155,6 @@ def _ready_execution_row(
155
155
 
156
156
 
157
157
  def _create_ready_execution(
158
- alias,
159
- job,
160
- *,
161
- backend_alias,
162
- queue_name=None,
163
- priority=None,
164
- ready_at=None,
165
- created_at=None,
166
- ):
167
- queue_name = _execution_queue_name(job, queue_name)
168
- return _create_ready_execution_locked(
169
- alias,
170
- job,
171
- backend_alias=backend_alias,
172
- queue_name=queue_name,
173
- priority=priority,
174
- ready_at=ready_at,
175
- created_at=created_at,
176
- check_conflicts=True,
177
- )
178
-
179
-
180
- def _create_ready_execution_locked(
181
158
  alias,
182
159
  job,
183
160
  *,
@@ -190,7 +167,6 @@ def _create_ready_execution_locked(
190
167
  ):
191
168
  if check_conflicts:
192
169
  _ensure_no_other_execution_state(alias, job)
193
- _lock_active_pauses(alias, backend_alias, {queue_name})
194
170
  return ReadyExecution.objects.using(alias).create(
195
171
  **_ready_execution_fields(
196
172
  job,
@@ -203,29 +179,16 @@ def _create_ready_execution_locked(
203
179
  )
204
180
 
205
181
 
206
- def _bulk_create_ready_executions_locked(alias, ready_rows, *, backend_alias, check_conflicts):
182
+ def _bulk_create_ready_executions(alias, ready_rows, *, check_conflicts):
207
183
  ready_rows = tuple(ready_rows)
208
184
  if not ready_rows:
209
185
  return None
210
186
 
211
187
  if check_conflicts:
212
188
  _ensure_job_ids_have_no_other_execution_state(alias, [row.job_id for row in ready_rows])
213
- _lock_active_pauses(alias, backend_alias, {row.queue_name for row in ready_rows})
214
189
  return _bulk_create(alias, ReadyExecution, ready_rows)
215
190
 
216
191
 
217
- def _ready_execution_rows(jobs, *, backend_alias, ready_at=None, created_at=None):
218
- return [
219
- _ready_execution_row(
220
- job,
221
- backend_alias=backend_alias,
222
- ready_at=ready_at,
223
- created_at=created_at,
224
- )
225
- for job in jobs
226
- ]
227
-
228
-
229
192
  def _scheduled_execution_row(job, *, backend_alias, scheduled_at=None, created_at=None):
230
193
  return ScheduledExecution(
231
194
  **_scheduled_execution_fields(
@@ -9,7 +9,6 @@ from django.utils import timezone
9
9
  from dj_queue.config import resolve_backend_config
10
10
  from dj_queue.db import (
11
11
  database_capabilities,
12
- get_database_alias,
13
12
  locked_queryset,
14
13
  retry_transient_database_errors,
15
14
  )
@@ -46,29 +45,26 @@ def claim_ready_jobs(
46
45
  queues: str | Sequence[str] | None = None,
47
46
  process: Process | None = None,
48
47
  backend_alias: str = "default",
49
- use_skip_locked: bool | None = None,
50
48
  config=None,
51
49
  ) -> list[ClaimedJob]:
52
50
  if limit <= 0:
53
51
  return []
54
52
 
55
- alias = get_database_alias(backend_alias, config=config)
56
- if use_skip_locked is None:
57
- use_skip_locked = resolve_backend_config(backend_alias, config).use_skip_locked
58
-
53
+ config = resolve_backend_config(backend_alias, config)
54
+ alias = config.database_alias
59
55
  claimed_jobs = retry_transient_database_errors(
60
56
  lambda: _claim_ready_jobs_once(
61
57
  limit=limit,
62
58
  queues=queues,
63
59
  process=process,
64
60
  backend_alias=backend_alias,
65
- use_skip_locked=use_skip_locked,
61
+ use_skip_locked=config.use_skip_locked,
66
62
  alias=alias,
67
63
  ),
68
64
  using=alias,
69
65
  )
70
66
 
71
- if event_logging_enabled(backend_alias=backend_alias):
67
+ if event_logging_enabled():
72
68
  for claimed_job in claimed_jobs:
73
69
  log_event(
74
70
  "job.claimed",
@@ -174,10 +170,6 @@ def _claim_ready_jobs_once(
174
170
  ]
175
171
 
176
172
 
177
- def _filter_queue_selectors(queryset, queues):
178
- return filter_by_queue_selectors(queryset, queues)
179
-
180
-
181
173
  def _select_ready_rows(queryset, *, limit, queues, use_skip_locked, alias, backend_alias):
182
174
  if selectors_match_all(queues):
183
175
  select_ready_row_ids = getattr(backend_sql(alias), "select_ready_row_ids", None)
@@ -195,36 +187,29 @@ def _select_ready_rows(queryset, *, limit, queues, use_skip_locked, alias, backe
195
187
 
196
188
  selectors = normalize_queue_selectors(queues)
197
189
  selected_rows = []
198
- selected_ids = set()
199
190
 
200
191
  star_index = selectors.index("*") if "*" in selectors else None
201
192
  ordered_selectors = selectors if star_index is None else selectors[:star_index]
202
193
 
203
194
  if ordered_selectors:
204
195
  if _selectors_are_exact_queues(ordered_selectors):
205
- rows = _select_exact_queue_rows(
196
+ selected_rows = _select_exact_queue_rows(
206
197
  queryset,
207
198
  ordered_selectors,
208
199
  limit=limit,
209
200
  use_skip_locked=use_skip_locked,
210
- selected_ids=selected_ids,
211
201
  alias=alias,
212
202
  backend_alias=backend_alias,
213
203
  )
214
204
  else:
215
- ordered = _ordered_selector_rows_queryset(
216
- queryset.exclude(pk__in=selected_ids),
217
- ordered_selectors,
218
- )
219
- rows = list(locked_queryset(ordered, use_skip_locked=use_skip_locked)[:limit])
220
- selected_rows.extend(rows)
221
- selected_ids.update(row.pk for row in rows)
205
+ ordered = _ordered_selector_rows_queryset(queryset, ordered_selectors)
206
+ selected_rows = list(locked_queryset(ordered, use_skip_locked=use_skip_locked)[:limit])
222
207
 
223
208
  remaining = limit - len(selected_rows)
224
209
  if remaining <= 0 or star_index is None:
225
210
  return selected_rows
226
211
 
227
- ordered = queryset.exclude(pk__in=selected_ids).order_by("-priority", "id")
212
+ ordered = queryset.exclude(pk__in=[row.pk for row in selected_rows]).order_by("-priority", "id")
228
213
  rows = list(locked_queryset(ordered, use_skip_locked=use_skip_locked)[:remaining])
229
214
  selected_rows.extend(rows)
230
215
  return selected_rows
@@ -240,22 +225,21 @@ def _select_exact_queue_rows(
240
225
  *,
241
226
  limit,
242
227
  use_skip_locked,
243
- selected_ids,
244
228
  alias,
245
229
  backend_alias,
246
230
  ):
247
- if not selected_ids:
248
- select_ready_rows = getattr(backend_sql(alias), "select_ready_rows_by_exact_queues", None)
249
- if select_ready_rows is not None:
250
- return select_ready_rows(
251
- alias,
252
- backend_alias=backend_alias,
253
- selectors=selectors,
254
- limit=limit,
255
- use_skip_locked=use_skip_locked,
256
- )
231
+ select_ready_rows = getattr(backend_sql(alias), "select_ready_rows_by_exact_queues", None)
232
+ if select_ready_rows is not None:
233
+ return select_ready_rows(
234
+ alias,
235
+ backend_alias=backend_alias,
236
+ selectors=selectors,
237
+ limit=limit,
238
+ use_skip_locked=use_skip_locked,
239
+ )
257
240
 
258
241
  selected_rows = []
242
+ selected_ids = set()
259
243
  for selector in selectors:
260
244
  remaining = limit - len(selected_rows)
261
245
  if remaining <= 0:
@@ -285,7 +269,7 @@ def _create_claimed_executions(alias, jobs, *, process, claimed_at):
285
269
 
286
270
 
287
271
  def _ordered_selector_rows_queryset(queryset, selectors):
288
- filtered = _filter_queue_selectors(queryset, selectors)
272
+ filtered = filter_by_queue_selectors(queryset, selectors)
289
273
  selector_rank = Case(
290
274
  *[
291
275
  When(queue_selector_condition((selector,)), then=Value(index))