Migrating from django-celery-beat
scripts/migrate_from_celery_beat.py is a one-shot tool that reads the schedules in a
django-celery-beat database and translates
them into Kailash WorkflowScheduler registrations.
What it does — and what it can’t
The tool translates the schedule (the timing and the enabled/disabled state). It
cannot translate what runs: a celery PeriodicTask points at a Celery task — a
Python callable like billing.tasks.run — while a Kailash schedule needs a
WorkflowBuilder. There is no mechanical way to turn a callable into a workflow, so wiring
each task to its replacement workflow is your step. The tool scaffolds it for you with an
explicit WIRE ME marker on every emitted registration.
Usage
python scripts/migrate_from_celery_beat.py \
--django-db postgresql://user:pass@host:5432/djangodb \
--out celery_beat_manifest.json \
--script register_schedules.py # optional starter script
Supported source databases: sqlite, postgresql, mysql (resolved through the kailash
ConnectionManager dialects — install the matching async driver for your source DB).
The tool produces two artifacts:
--outmanifest (JSON, required): one entry perPeriodicTask— its name, the source celery task name, the translated trigger (cron/interval), the enabled flag, and any per-row migration warnings. This is the machine-readable record.--scriptstarter (Python, optional): oneschedule_cron(...)/schedule_interval(...)call per row, with theWorkflowBuilderleft as aWIRE MEplaceholder. It does not run as-is — fill in each workflow first.
Worked example
Given a celery-beat DB with a weekly cron task and a disabled polling interval:
$ python scripts/migrate_from_celery_beat.py --django-db "sqlite:///django.sqlite3" \
--out manifest.json --script register.py
2 PeriodicTask rows: 1 cron, 1 interval, 0 unsupported; 1 disabled; 1 with warnings.
[warn] weekly-billing: source crontab timezone is 'America/New_York'; WorkflowScheduler
applies one GLOBAL timezone (default UTC) — per-task timezone is NOT preserved. ...
manifest.json:
[
{
"name": "health-poll",
"celery_task": "ops.tasks.poll",
"trigger": "interval",
"cron_expression": null,
"interval_seconds": 300.0,
"enabled": false,
"warnings": []
},
{
"name": "weekly-billing",
"celery_task": "billing.tasks.run",
"trigger": "cron",
"cron_expression": "0 9 15 * 1",
"interval_seconds": null,
"enabled": true,
"warnings": ["source crontab timezone is 'America/New_York'; ..."]
}
]
register.py (excerpt — complete the WIRE ME placeholders, then run):
from kailash.runtime.scheduler import WorkflowScheduler
from kailash.runtime.scheduler_admin import SchedulerAdminAPI
from kailash.workflow.builder import WorkflowBuilder
scheduler = WorkflowScheduler() # configure job_store / timezone as needed
admin = SchedulerAdminAPI(scheduler)
# --- weekly-billing (celery task: billing.tasks.run) ---
# WIRE ME: build the WorkflowBuilder that replaces celery task 'billing.tasks.run'
wb_weekly_billing = WorkflowBuilder() # <-- replace with real workflow
sid_weekly_billing = scheduler.schedule_cron(wb_weekly_billing, '0 9 15 * 1', name='weekly-billing')
Translation rules
celery-beat source |
Kailash target |
Notes |
|---|---|---|
|
|
Fields are reordered: celery stores |
|
|
|
|
register, then |
The schedule is created paused. |
per-task |
(warning) |
|
Solar / Clocked schedules |
(warning, not translated) |
Out of scope — migrate these manually. |
Notes
celery’s
CrontabScheduleis minute-granularity (no seconds field), so there is no sub-minute cron to translate or reject.A sub-second
IntervalSchedule(e.g.every=500, period=microseconds) is valid forschedule_intervalbut flagged as likely-impractical.The tool reads with static
SELECTs against the fixeddjango_celery_beat_*table names — no schema changes are made to the source database.