dj-queue 0.8.1__tar.gz → 0.9.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 (77) hide show
  1. {dj_queue-0.8.1 → dj_queue-0.9.0}/PKG-INFO +65 -64
  2. {dj_queue-0.8.1 → dj_queue-0.9.0}/README.md +64 -61
  3. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/config.py +11 -9
  4. dj_queue-0.9.0/dj_queue/cron.py +1393 -0
  5. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/models/recurring.py +3 -2
  6. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/observability.py +2 -2
  7. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/operations/recurring.py +2 -4
  8. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/runtime/scheduler.py +2 -4
  9. {dj_queue-0.8.1 → dj_queue-0.9.0}/pyproject.toml +1 -3
  10. {dj_queue-0.8.1 → dj_queue-0.9.0}/LICENSE +0 -0
  11. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/__init__.py +0 -0
  12. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/admin.py +0 -0
  13. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/api.py +0 -0
  14. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/apps.py +0 -0
  15. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/backend.py +0 -0
  16. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/contrib/__init__.py +0 -0
  17. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/contrib/asgi.py +0 -0
  18. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/contrib/gunicorn.py +0 -0
  19. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/contrib/prometheus.py +0 -0
  20. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/dashboard.py +0 -0
  21. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/db.py +0 -0
  22. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/exceptions.py +0 -0
  23. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/hooks.py +0 -0
  24. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/log.py +0 -0
  25. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/management/__init__.py +0 -0
  26. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/management/commands/__init__.py +0 -0
  27. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/management/commands/dj_queue.py +0 -0
  28. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/management/commands/dj_queue_health.py +0 -0
  29. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/management/commands/dj_queue_prune.py +0 -0
  30. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/migrations/0001_initial.py +0 -0
  31. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/migrations/0002_pause_semaphore.py +0 -0
  32. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/migrations/0003_recurringtask_recurringexecution.py +0 -0
  33. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/migrations/0004_dashboard.py +0 -0
  34. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/migrations/0005_remove_recurringexecution_dj_queue_recurring_executions_task_key_run_at_unique_and_more.py +0 -0
  35. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/migrations/0006_blockedexecution_dj_queue_bl_concurr_2d8393_idx_and_more.py +0 -0
  36. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/migrations/0007_recurringtask_next_run_at.py +0 -0
  37. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/migrations/0008_remove_blockedexecution_dj_queue_bl_concurr_1ce730_idx_and_more.py +0 -0
  38. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/migrations/__init__.py +0 -0
  39. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/models/__init__.py +0 -0
  40. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/models/jobs.py +0 -0
  41. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/models/runtime.py +0 -0
  42. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/operations/__init__.py +0 -0
  43. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/operations/_helpers.py +0 -0
  44. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/operations/_insert.py +0 -0
  45. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/operations/cleanup.py +0 -0
  46. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/operations/concurrency.py +0 -0
  47. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/operations/jobs.py +0 -0
  48. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/operations/queues.py +0 -0
  49. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/routers.py +0 -0
  50. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/runtime/__init__.py +0 -0
  51. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/runtime/base.py +0 -0
  52. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/runtime/connection_budget.py +0 -0
  53. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/runtime/dispatcher.py +0 -0
  54. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/runtime/errors.py +0 -0
  55. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/runtime/interruptible.py +0 -0
  56. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/runtime/notify.py +0 -0
  57. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/runtime/pidfile.py +0 -0
  58. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/runtime/pool.py +0 -0
  59. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/runtime/procline.py +0 -0
  60. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/runtime/supervisor.py +0 -0
  61. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/runtime/worker.py +0 -0
  62. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/templates/admin/dj_queue/_dashboard_process_rows.html +0 -0
  63. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/templates/admin/dj_queue/_dashboard_recurring_rows.html +0 -0
  64. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/templates/admin/dj_queue/_dashboard_section_table.html +0 -0
  65. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/templates/admin/dj_queue/_dashboard_semaphore_rows.html +0 -0
  66. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/templates/admin/dj_queue/_paginator.html +0 -0
  67. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/templates/admin/dj_queue/_queue_controls.html +0 -0
  68. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/templates/admin/dj_queue/_sortable_header_cells.html +0 -0
  69. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/templates/admin/dj_queue/change_form.html +0 -0
  70. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/templates/admin/dj_queue/change_list.html +0 -0
  71. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/templates/admin/dj_queue/dashboard.html +0 -0
  72. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/templates/admin/dj_queue/includes/fieldset.html +0 -0
  73. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/templates/admin/dj_queue/queue_jobs.html +0 -0
  74. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/templatetags/__init__.py +0 -0
  75. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/templatetags/dj_queue_admin.py +0 -0
  76. {dj_queue-0.8.1 → dj_queue-0.9.0}/dj_queue/urls.py +0 -0
  77. {dj_queue-0.8.1 → dj_queue-0.9.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.8.1
3
+ Version: 0.9.0
4
4
  Summary: Database-backed task queue backend for Django’s Tasks framework.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -13,9 +13,7 @@ Classifier: Programming Language :: Python :: 3.12
13
13
  Classifier: Programming Language :: Python :: 3.13
14
14
  Classifier: Programming Language :: Python :: 3.14
15
15
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
- Requires-Dist: croniter>=6.2.2
17
16
  Requires-Dist: django>=6.0.0
18
- Requires-Dist: pyyaml>=6.0.3
19
17
  Requires-Dist: psycopg>=3.3.3 ; extra == 'postgres'
20
18
  Requires-Dist: prometheus-client>=0.4.0 ; extra == 'prometheus'
21
19
  Requires-Python: >=3.12
@@ -408,15 +406,18 @@ unschedule_recurring_task("tenant_42_report")
408
406
 
409
407
  Dynamic recurring tasks require
410
408
  `TASKS[backend_alias]["OPTIONS"]["scheduler"]["dynamic_tasks_enabled"] = True`
411
- or the equivalent `scheduler.dynamic_tasks_enabled: true` in the optional YAML
409
+ or the equivalent `scheduler.dynamic_tasks_enabled = true` in the optional TOML
412
410
  config.
413
411
 
414
- The scheduler is part of the normal `dj_queue` runtime. You do not run a
415
- separate recurring service.
412
+ The scheduler is part of the normal `dj_queue` runtime. You do not run a separate recurring service.
416
413
 
417
414
  Notes:
418
415
 
419
- - schedules are cron expressions
416
+ - schedules are [Fugit](https://github.com/floraison/fugit#fugitcron)-style cron or cronish natural-language expressions
417
+ - cron supports five fields or an optional leading seconds field, presets like `@daily`, optional timezone suffixes, wraparound ranges, `L`/`last` and negative monthdays, weekday `#` and `%` extensions, and normal day-of-month/day-of-week OR semantics
418
+ - add `&` to either day field to require day-of-month and day-of-week to both match
419
+ - natural-language schedules support `every`, `at`, `on`, and `from` forms such as `every day at noon`, `every weekday at five`, `every 5 minutes`, `every month on day 2 at 10:00`, `from monday to friday at 9`, and `at minute 5`
420
+ - natural-language schedules that expand to multiple cron expressions, such as `every day at 16:15 and 18:30`, are treated as the union of those schedules
420
421
  - recurring task keys are scoped per backend alias
421
422
  - only dynamic tasks can be unscheduled at runtime; unscheduling a static task returns `0`
422
423
  - Django admin exposes the same unschedule operation on recurring-task list and detail views
@@ -810,83 +811,83 @@ Configuration precedence is explicit:
810
811
 
811
812
  - CLI overrides
812
813
  - environment variables
813
- - YAML file pointed to by `DJ_QUEUE_CONFIG`
814
+ - TOML file pointed to by `DJ_QUEUE_CONFIG`
814
815
  - Django `TASKS` settings
815
816
 
816
- ### YAML file config
817
+ ### TOML file config
817
818
 
818
819
  ```bash
819
820
  # via cli
820
- python manage.py dj_queue --config /etc/dj_queue.yml
821
+ python manage.py dj_queue --config /etc/dj_queue.toml
821
822
 
822
823
  # or via environment variable
823
- DJ_QUEUE_CONFIG=/etc/dj_queue.yml python manage.py dj_queue
824
+ DJ_QUEUE_CONFIG=/etc/dj_queue.toml python manage.py dj_queue
824
825
  ```
825
826
 
826
- The YAML file is an overlay on `TASKS[backend_alias]["OPTIONS"]`. It supports
827
+ The TOML file is an overlay on `TASKS[backend_alias]["OPTIONS"]`. It supports
827
828
  two shapes:
828
829
 
829
830
  - a flat mapping of option values for the selected backend alias
830
831
  - a `backends` mapping keyed by backend alias, where only the selected alias is applied
831
832
 
833
+ TOML has no `null` value. Omit optional settings to keep their existing defaults.
834
+
832
835
  Flat mapping example:
833
836
 
834
- ```yaml
835
- mode: async
836
- database_alias: queue
837
- preserve_finished_jobs: true
838
- clear_finished_jobs_after: 86400
839
- clear_failed_jobs_after: null
840
- clear_recurring_executions_after: null
841
- listen_notify: true
842
- silence_polling: true
843
-
844
- workers:
845
- - queues: ["default", "email*"]
846
- threads: 8
847
- processes: 1
848
- polling_interval: 0.1
849
-
850
- dispatchers:
851
- - batch_size: 500
852
- polling_interval: 1
853
- concurrency_maintenance: true
854
- concurrency_maintenance_interval: 600
855
-
856
- scheduler:
857
- dynamic_tasks_enabled: true
858
- polling_interval: 5
859
-
860
- recurring:
861
- nightly_cleanup:
862
- task_path: myapp.tasks.cleanup
863
- schedule: "0 3 * * *"
864
- queue_name: maintenance
865
- priority: -5
866
- description: nightly cleanup
837
+ ```toml
838
+ mode = "async"
839
+ database_alias = "queue"
840
+ preserve_finished_jobs = true
841
+ clear_finished_jobs_after = 86400
842
+ listen_notify = true
843
+ silence_polling = true
844
+
845
+ [[workers]]
846
+ queues = ["default", "email*"]
847
+ threads = 8
848
+ processes = 1
849
+ polling_interval = 0.1
850
+
851
+ [[dispatchers]]
852
+ batch_size = 500
853
+ polling_interval = 1
854
+ concurrency_maintenance = true
855
+ concurrency_maintenance_interval = 600
856
+
857
+ [scheduler]
858
+ dynamic_tasks_enabled = true
859
+ polling_interval = 5
860
+
861
+ [recurring.nightly_cleanup]
862
+ task_path = "myapp.tasks.cleanup"
863
+ schedule = "0 3 * * *"
864
+ queue_name = "maintenance"
865
+ priority = -5
866
+ description = "nightly cleanup"
867
867
  ```
868
868
 
869
869
  Multi-backend overlay example:
870
870
 
871
- ```yaml
872
- backends:
873
- default:
874
- mode: async
875
- database_alias: default
876
- workers:
877
- - queues: ["default", "email*"]
878
- threads: 8
879
- processes: 1
880
- polling_interval: 0.1
881
-
882
- critical:
883
- mode: fork
884
- database_alias: queue
885
- workers:
886
- - queues: ["alerts", "critical-review"]
887
- threads: 2
888
- processes: 1
889
- polling_interval: 0.05
871
+ ```toml
872
+ [backends.default]
873
+ mode = "async"
874
+ database_alias = "default"
875
+
876
+ [[backends.default.workers]]
877
+ queues = ["default", "email*"]
878
+ threads = 8
879
+ processes = 1
880
+ polling_interval = 0.1
881
+
882
+ [backends.critical]
883
+ mode = "fork"
884
+ database_alias = "queue"
885
+
886
+ [[backends.critical.workers]]
887
+ queues = ["alerts", "critical-review"]
888
+ threads = 2
889
+ processes = 1
890
+ polling_interval = 0.05
890
891
  ```
891
892
 
892
893
  Environment overrides currently supported by `dj_queue` itself:
@@ -380,15 +380,18 @@ unschedule_recurring_task("tenant_42_report")
380
380
 
381
381
  Dynamic recurring tasks require
382
382
  `TASKS[backend_alias]["OPTIONS"]["scheduler"]["dynamic_tasks_enabled"] = True`
383
- or the equivalent `scheduler.dynamic_tasks_enabled: true` in the optional YAML
383
+ or the equivalent `scheduler.dynamic_tasks_enabled = true` in the optional TOML
384
384
  config.
385
385
 
386
- The scheduler is part of the normal `dj_queue` runtime. You do not run a
387
- separate recurring service.
386
+ The scheduler is part of the normal `dj_queue` runtime. You do not run a separate recurring service.
388
387
 
389
388
  Notes:
390
389
 
391
- - schedules are cron expressions
390
+ - schedules are [Fugit](https://github.com/floraison/fugit#fugitcron)-style cron or cronish natural-language expressions
391
+ - cron supports five fields or an optional leading seconds field, presets like `@daily`, optional timezone suffixes, wraparound ranges, `L`/`last` and negative monthdays, weekday `#` and `%` extensions, and normal day-of-month/day-of-week OR semantics
392
+ - add `&` to either day field to require day-of-month and day-of-week to both match
393
+ - natural-language schedules support `every`, `at`, `on`, and `from` forms such as `every day at noon`, `every weekday at five`, `every 5 minutes`, `every month on day 2 at 10:00`, `from monday to friday at 9`, and `at minute 5`
394
+ - natural-language schedules that expand to multiple cron expressions, such as `every day at 16:15 and 18:30`, are treated as the union of those schedules
392
395
  - recurring task keys are scoped per backend alias
393
396
  - only dynamic tasks can be unscheduled at runtime; unscheduling a static task returns `0`
394
397
  - Django admin exposes the same unschedule operation on recurring-task list and detail views
@@ -782,83 +785,83 @@ Configuration precedence is explicit:
782
785
 
783
786
  - CLI overrides
784
787
  - environment variables
785
- - YAML file pointed to by `DJ_QUEUE_CONFIG`
788
+ - TOML file pointed to by `DJ_QUEUE_CONFIG`
786
789
  - Django `TASKS` settings
787
790
 
788
- ### YAML file config
791
+ ### TOML file config
789
792
 
790
793
  ```bash
791
794
  # via cli
792
- python manage.py dj_queue --config /etc/dj_queue.yml
795
+ python manage.py dj_queue --config /etc/dj_queue.toml
793
796
 
794
797
  # or via environment variable
795
- DJ_QUEUE_CONFIG=/etc/dj_queue.yml python manage.py dj_queue
798
+ DJ_QUEUE_CONFIG=/etc/dj_queue.toml python manage.py dj_queue
796
799
  ```
797
800
 
798
- The YAML file is an overlay on `TASKS[backend_alias]["OPTIONS"]`. It supports
801
+ The TOML file is an overlay on `TASKS[backend_alias]["OPTIONS"]`. It supports
799
802
  two shapes:
800
803
 
801
804
  - a flat mapping of option values for the selected backend alias
802
805
  - a `backends` mapping keyed by backend alias, where only the selected alias is applied
803
806
 
807
+ TOML has no `null` value. Omit optional settings to keep their existing defaults.
808
+
804
809
  Flat mapping example:
805
810
 
806
- ```yaml
807
- mode: async
808
- database_alias: queue
809
- preserve_finished_jobs: true
810
- clear_finished_jobs_after: 86400
811
- clear_failed_jobs_after: null
812
- clear_recurring_executions_after: null
813
- listen_notify: true
814
- silence_polling: true
815
-
816
- workers:
817
- - queues: ["default", "email*"]
818
- threads: 8
819
- processes: 1
820
- polling_interval: 0.1
821
-
822
- dispatchers:
823
- - batch_size: 500
824
- polling_interval: 1
825
- concurrency_maintenance: true
826
- concurrency_maintenance_interval: 600
827
-
828
- scheduler:
829
- dynamic_tasks_enabled: true
830
- polling_interval: 5
831
-
832
- recurring:
833
- nightly_cleanup:
834
- task_path: myapp.tasks.cleanup
835
- schedule: "0 3 * * *"
836
- queue_name: maintenance
837
- priority: -5
838
- description: nightly cleanup
811
+ ```toml
812
+ mode = "async"
813
+ database_alias = "queue"
814
+ preserve_finished_jobs = true
815
+ clear_finished_jobs_after = 86400
816
+ listen_notify = true
817
+ silence_polling = true
818
+
819
+ [[workers]]
820
+ queues = ["default", "email*"]
821
+ threads = 8
822
+ processes = 1
823
+ polling_interval = 0.1
824
+
825
+ [[dispatchers]]
826
+ batch_size = 500
827
+ polling_interval = 1
828
+ concurrency_maintenance = true
829
+ concurrency_maintenance_interval = 600
830
+
831
+ [scheduler]
832
+ dynamic_tasks_enabled = true
833
+ polling_interval = 5
834
+
835
+ [recurring.nightly_cleanup]
836
+ task_path = "myapp.tasks.cleanup"
837
+ schedule = "0 3 * * *"
838
+ queue_name = "maintenance"
839
+ priority = -5
840
+ description = "nightly cleanup"
839
841
  ```
840
842
 
841
843
  Multi-backend overlay example:
842
844
 
843
- ```yaml
844
- backends:
845
- default:
846
- mode: async
847
- database_alias: default
848
- workers:
849
- - queues: ["default", "email*"]
850
- threads: 8
851
- processes: 1
852
- polling_interval: 0.1
853
-
854
- critical:
855
- mode: fork
856
- database_alias: queue
857
- workers:
858
- - queues: ["alerts", "critical-review"]
859
- threads: 2
860
- processes: 1
861
- polling_interval: 0.05
845
+ ```toml
846
+ [backends.default]
847
+ mode = "async"
848
+ database_alias = "default"
849
+
850
+ [[backends.default.workers]]
851
+ queues = ["default", "email*"]
852
+ threads = 8
853
+ processes = 1
854
+ polling_interval = 0.1
855
+
856
+ [backends.critical]
857
+ mode = "fork"
858
+ database_alias = "queue"
859
+
860
+ [[backends.critical.workers]]
861
+ queues = ["alerts", "critical-review"]
862
+ threads = 2
863
+ processes = 1
864
+ polling_interval = 0.05
862
865
  ```
863
866
 
864
867
  Environment overrides currently supported by `dj_queue` itself:
@@ -1,6 +1,7 @@
1
1
  import json
2
2
  import math
3
3
  import os
4
+ import tomllib
4
5
  import warnings
5
6
  from collections.abc import Mapping, Sequence
6
7
  from dataclasses import asdict, dataclass, field, replace
@@ -8,12 +9,12 @@ from functools import lru_cache
8
9
  from pathlib import Path
9
10
  from typing import Any
10
11
 
11
- import yaml
12
- from croniter import croniter
13
12
  from django.conf import settings
14
13
  from django.core.exceptions import ImproperlyConfigured
15
14
  from django.utils.module_loading import import_string
16
15
 
16
+ from dj_queue.cron import is_valid_cron
17
+
17
18
  DEFAULT_WORKER = {
18
19
  "queues": "*",
19
20
  "threads": 3,
@@ -319,7 +320,7 @@ def _resolved_options(
319
320
  resolved_options.update(settings_options)
320
321
 
321
322
  config_path = cli_overrides.get("config") or env.get("DJ_QUEUE_CONFIG")
322
- resolved_options.update(_load_yaml_options(config_path, backend_alias=backend_alias))
323
+ resolved_options.update(_load_toml_options(config_path, backend_alias=backend_alias))
323
324
 
324
325
  env_mode = env.get("DJ_QUEUE_MODE")
325
326
  if env_mode is not None:
@@ -332,15 +333,16 @@ def _resolved_options(
332
333
  return resolved_options
333
334
 
334
335
 
335
- def _load_yaml_options(config_path: Any, *, backend_alias: str) -> dict[str, Any]:
336
+ def _load_toml_options(config_path: Any, *, backend_alias: str) -> dict[str, Any]:
336
337
  if not config_path:
337
338
  return {}
338
339
 
339
- config_payload = yaml.safe_load(Path(config_path).read_text(encoding="utf-8"))
340
- if config_payload is None:
341
- return {}
340
+ try:
341
+ config_payload = tomllib.loads(Path(config_path).read_text(encoding="utf-8"))
342
+ except tomllib.TOMLDecodeError as exc:
343
+ raise ImproperlyConfigured(f"DJ_QUEUE_CONFIG TOML is invalid: {exc}") from exc
342
344
  if not isinstance(config_payload, dict):
343
- raise ImproperlyConfigured("DJ_QUEUE_CONFIG must point to a YAML mapping")
345
+ raise ImproperlyConfigured("DJ_QUEUE_CONFIG must point to a TOML mapping")
344
346
 
345
347
  raw_backends = config_payload.get("backends")
346
348
  if raw_backends is None:
@@ -519,7 +521,7 @@ def _build_recurring_config(raw_recurring: Any) -> dict[str, RecurringTaskConfig
519
521
  schedule = raw_entry.get("schedule")
520
522
  if not task_path or not schedule:
521
523
  raise ImproperlyConfigured(f"recurring task {key!r} requires task_path and schedule")
522
- if not croniter.is_valid(str(schedule)):
524
+ if not is_valid_cron(str(schedule)):
523
525
  raise ImproperlyConfigured(f"recurring task {key!r} has an invalid cron schedule")
524
526
 
525
527
  recurring[str(key)] = RecurringTaskConfig(