django-ox
A database-backed worker backend for Django's Tasks framework (django.tasks, Django 6.0+).
Django 6.0 ships the Tasks API but no production backend: the built-in
ImmediateBackend and DummyBackend are for development and testing only.
django-ox stores tasks in your existing database and runs them with a
separate worker process, so you get a durable queue without adding a broker.
Durability model
enqueue() is a single INSERT on your default database connection, so it
participates in the caller's open transaction. A task enqueued inside
transaction.atomic() becomes visible to workers only when the transaction
commits, and disappears on rollback. There is no window where business data
exists without its task, or a task without its data, and no
transaction.on_commit() boilerplate. Execution is at-least-once: workers
claim tasks with SELECT ... FOR UPDATE SKIP LOCKED on databases that support
it (PostgreSQL, MySQL 8+) and an atomic compare-and-set UPDATE elsewhere
(including SQLite), and a reaper returns tasks whose worker died to the queue.
Failed tasks retry with exponential backoff up to a configurable attempt
limit, keeping the full traceback of every attempt.
Install
Requires Python 3.12+ and Django 6.0+.
pip install django-ox
INSTALLED_APPS = [
# ...
"django_ox",
]
TASKS = {
"default": {
"BACKEND": "django_ox.backend.OxBackend",
"QUEUES": ["default", "emails"], # [] allows any queue name
"OPTIONS": {
"MAX_ATTEMPTS": 3, # executions per task before FAILED
"LOCK_TIMEOUT": 300, # seconds before a dead worker's task is reclaimed
"BACKOFF_INITIAL": 5, # first retry delay, seconds; doubles per attempt
"BACKOFF_MAX": 600, # retry delay ceiling, seconds
},
}
}
Then run migrations:
python manage.py migrate django_ox
Quickstart
Tasks are plain django.tasks tasks; django-ox adds nothing to learn on the
producer side.
from django.tasks import task
@task
def send_welcome_email(user_id): ...
result = send_welcome_email.enqueue(user_id=42)
result.refresh() # later: status, return_value, errors
Run a worker:
python manage.py ox_worker
Worker CLI
| Flag | Default | Meaning |
|---|---|---|
--backend |
default |
Backend alias from the TASKS setting. |
--queues |
all configured queues | Comma-separated queue names to process. |
--concurrency |
1 |
Tasks executed concurrently (thread pool). |
--interval |
1.0 |
Polling interval in seconds when idle. |
--lock-timeout |
backend LOCK_TIMEOUT |
Seconds before a stuck task is reclaimed. |
On SIGTERM or SIGINT the worker stops claiming, finishes in-flight tasks, then exits. A second signal forces an immediate exit.
Pruning
Finished task rows stay in the table until pruned. Run ox_prune on your
own schedule (cron, systemd timer):
python manage.py ox_prune --older-than 7d
| Flag | Default | Meaning |
|---|---|---|
--older-than |
7d |
Minimum time since the task finished. Accepts 7d, 24h, 90m, 45s, or a plain number of seconds. |
--include-failed |
off | Also delete FAILED rows. By default they are kept: they hold the per-attempt tracebacks. |
--batch-size |
1000 |
Rows per DELETE statement, so pruning a large table never takes a long lock or builds a giant IN clause. |
--dry-run |
off | Report how many rows would be deleted without deleting any. |
Only SUCCESSFUL rows (and, with --include-failed, FAILED rows) past the
cutoff are deleted. READY and RUNNING rows are never touched, whatever their
age. Old rows from the recurring-schedule tick log are cleared with the same
cutoff, always keeping each schedule's most recent tick.
Health and monitoring
django_ox.stats exposes queue metrics as plain functions, each a single
ORM query: per-queue status counts, backlog depth and age, throughput,
and failure rate. The ox_health command turns thresholds on those
numbers into an exit code for cron alerting and container probes:
python manage.py ox_health --max-backlog 1000 --max-age 600
| Flag | Default | Meaning |
|---|---|---|
--queue |
all queues | Restrict the checks to one queue. |
--max-backlog |
off | Fail when more than this many READY tasks are eligible to run. |
--max-age |
off | Fail when the oldest waiting task has waited longer than this many seconds. |
--worker-timeout |
off | Fail when no worker has claimed a task within this many seconds. |
Worker lifecycle events (claim, start, success, retry, failure, reclaim,
shutdown) log to the django_ox logger with stable extra keys (task id,
queue, attempt, duration), ready for JSON log handlers.
Recurring tasks
Schedules are declared in settings, next to the backend they enqueue through, and deploy with your code. There are no rows to edit by hand and no separate scheduler process to keep alive:
TASKS = {
"default": {
"BACKEND": "django_ox.backend.OxBackend",
"QUEUES": ["default", "emails"],
"OPTIONS": {
"SCHEDULES": {
"nightly-report": {
"task": "reports.tasks.build_report",
"cron": "0 3 * * *",
"kwargs": {"full": True},
},
"warm-cache": {
"task": "core.tasks.warm_cache",
"cron": "*/15 * * * *",
},
},
},
}
}
Each tick enqueues a normal task instance, which workers claim and execute through the ordinary queue: retries, backoff, priorities and the result store all apply unchanged. Every running worker doubles as the scheduler, and a unique constraint on (schedule name, tick time) makes each tick fire exactly once however many workers are polling.
| Key | Required | Meaning |
|---|---|---|
task |
yes | Dotted path to a @task callable, e.g. "reports.tasks.build_report". |
cron |
yes | Five-field cron expression. |
args, kwargs |
no | JSON-serializable arguments passed to each enqueue. |
queue_name |
no | Queue override; defaults to the task's own queue. |
priority |
no | Priority override (-100 to 100). |
Cron expressions use the classic five-field syntax: *, lists (1,15),
ranges (mon-fri), steps (*/15), month and weekday names, 0 or 7 for
Sunday, and the @hourly, @daily, @weekly, @monthly and @yearly
shortcuts. When both day-of-month and day-of-week are restricted, a day
matches if either field does, as in vixie cron. Times are wall-clock in
your TIME_ZONE.
Misconfigured schedules (a task path that does not import, an expression
that can never fire) fail at worker startup and in manage.py check, not
silently at dispatch time.
Missed ticks: if every worker was down when a tick passed, the latest missed tick fires once on recovery and older ones are skipped, so a nightly job still runs after an unlucky deploy window but a backlog never stampedes. A newly deployed schedule waits for its next tick rather than firing for a time before it existed.
Behavior details
run_after(deferred tasks),priority(-100 to 100, higher runs first),get_result()and the async variants are all supported; the backend declaressupports_defer,supports_priority,supports_get_resultandsupports_async_taskaccordingly.- Retry state is visible in the database: attempts, per-attempt tracebacks,
and the next scheduled run (
run_after). - Because execution is at-least-once, tasks should be idempotent. A task is retried both when it raises and when its worker dies mid-run.
- Concurrency uses a thread pool. That fits I/O-bound tasks (email, HTTP,
ORM); for CPU-bound work, run multiple worker processes with
--concurrency=1instead.
Not yet supported
- Task revocation or cancellation after enqueue.
- Multi-database routing (tasks are stored on the default database for the model).
- Rate limiting, batching, and a dashboard.
Stability
What counts as public API, the pre-1.0 versioning and deprecation policy, and the supported Python and Django versions are documented in docs/stability.md.
License
BSD 3-Clause.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_ox-0.1.0.tar.gz.
File metadata
- Download URL: django_ox-0.1.0.tar.gz
- Upload date:
- Size: 27.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
27f7a3dd9bbab4b07a763cbd780d71ce624866906329cf91e077dcefaaabf7ea
|
|
| MD5 |
9d9cd9a2037baec89b439fd8c6dfee36
|
|
| BLAKE2b-256 |
204dc47306a9c836325a37d1c0e69d901a5dd97b71f9731ee1c8b69904f2ca10
|
Provenance
The following attestation bundles were made for django_ox-0.1.0.tar.gz:
Publisher:
release.yml on oxpull/django-ox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_ox-0.1.0.tar.gz -
Subject digest:
27f7a3dd9bbab4b07a763cbd780d71ce624866906329cf91e077dcefaaabf7ea - Sigstore transparency entry: 2485344845
- Sigstore integration time:
-
Permalink:
oxpull/django-ox@30ace76f3c777c751ba52a64cc923f7ea9ea2332 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/oxpull
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@30ace76f3c777c751ba52a64cc923f7ea9ea2332 -
Trigger Event:
push
-
Statement type:
File details
Details for the file django_ox-0.1.0-py3-none-any.whl.
File metadata
- Download URL: django_ox-0.1.0-py3-none-any.whl
- Upload date:
- Size: 32.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
42875577669820fe22253d93e32081670508b641769c72c1e021f1f33bc877a3
|
|
| MD5 |
6b802bd7af7a58aaf7e493ed9ec8bad7
|
|
| BLAKE2b-256 |
7c2f7479fcebfd8018ef673bf47df54439e30753cef15fd6679558247357efc0
|
Provenance
The following attestation bundles were made for django_ox-0.1.0-py3-none-any.whl:
Publisher:
release.yml on oxpull/django-ox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_ox-0.1.0-py3-none-any.whl -
Subject digest:
42875577669820fe22253d93e32081670508b641769c72c1e021f1f33bc877a3 - Sigstore transparency entry: 2485344954
- Sigstore integration time:
-
Permalink:
oxpull/django-ox@30ace76f3c777c751ba52a64cc923f7ea9ea2332 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/oxpull
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@30ace76f3c777c751ba52a64cc923f7ea9ea2332 -
Trigger Event:
push
-
Statement type: