Repository navigation
Releases: Pythondeveloper6/celery-manager
Release list
celery-manager 0.1.0a4
Fourth alpha of celery-manager: a redesigned dashboard, pausing whole schedules (indefinitely or until a set time), Insights, workers and queue controls, and versioned schema migrations.
pip install --pre -U celery-managerUpgrading: this release adds schema migrations
0002–0005. By default they run automatically at startup, and that needs a database user with DDL rights. To run them as a deploy step instead, setauto_migrate=Falseand runcelery-manager migrate --db-url .... Databases from 0.1.0a1–0.1.0a3 are adopted automatically and keep their data.
Not yet verified against real infrastructure: workers, rate limits and queue controls (tested against stand-ins plus a throwaway Redis); the workflow tree from a live chord; and the MySQL migration lock.
Added
-
Pause and resume a whole schedule.
POST /api/schedules/{name}/pause
andPOST /api/schedules/{name}/resume(also
celery_manager.control.schedules.pause_schedule/resume_schedule).
While a schedule is paused, every firing is recorded as askippedrun and
the task body does not run — the same mechanism as an occurrence skip.
Both endpoints are idempotent, return404for an unknown schedule, and
writepause_schedule/resume_scheduleaudit rows.GET /api/schedules
items gainpaused,paused_atandpaused_by; each occurrence from
GET /api/schedules/{name}/occurrencesgainspaused. The frontend API
client gainspauseSchedule()/resumeSchedule(). Limitations:- only
@track-decorated tasks are affected — celery-manager does not
own beat, so beat keeps firing and an undecorated task still runs; - resume waits for the next regular beat firing. It doesn't run the task
now, doesn't reset beat's interval clock, and doesn't catch up firings
missed while paused (they were recorded asskipped).
The pause survives restarts: the startup sync from
beat_scheduleleaves
it alone. - only
-
Pause/Resume in the dashboard. Each schedule on the Schedules page has
a Pause button (with a confirm step) or a Resume button and a Paused badge.
A paused schedule's page shows who paused it and when, and marks upcoming
runs as "Will be skipped". -
Task list filters in the URL, saved views, copy link. Status tab,
search, group-by, date range and history-mode page are reflected in the
hash-route query string (#/?status=failed&q=smtp), restored on load and
on back/forward. Discrete choices push a history entry; typing in search
replaces. Copy link copies the view URL. Views saves named filter
sets per browser inlocalStorage(rename/delete supported; no backend).
Filter state moved intouseTaskFilters. -
Export task runs.
GET /api/tasks/export?format=csv|jsonstreams every
run matching the list filters, capped at the new
CeleryManagerConfig.export_max_rows(default 50000) with an
X-Export-Truncatedheader, CSV formula-injection escaping, a timestamped
filename, and anexport_tasksaudit row. The task list gains an Export
menu. -
Insights: trends, per-task pages and grouped failures. New Insights page
with 1h/24h/7d/30d ranges, KPIs, SVG charts (runs by status, p50/p95
runtime, each with a text summary and table fallback) and a Failures tab
that groups failed runs by a normalised exception signature, with open,
show-runs and retry-all-in-group actions. A task page at
/task-types/:taskName(linked from the task detail title and the Insights
top-task lists) shows the same trends for one task plus recent runs and top
errors. Backed byGET /api/analytics/overview,/api/analytics/failures
and/api/analytics/tasks/{task_name}, computed at query time (no schema
change; scans are capped, see Insights). -
Start a task from the dashboard. A Start task button on the Task
runs page opens a sheet with a searchable task picker, args/kwargs JSON
editors prefilled from the task's signature with live validation, and an
optional queue and countdown/ETA; submitting opens the new run. New
GET /api/tasks/registeredlists registered tasks (excludingcelery.*)
with best-effort signature, queue and docstring summary.
POST /api/tasks/startnow accepts optionalqueue,countdownandeta,
and seeds aqueuedrun row so the run's page exists straight away. -
Retry with changed arguments.
POST /api/tasks/{id}/retrytakes an
optional{"args", "kwargs"}body (no body behaves as before); retry
lineage is recorded as usual and the audit row notes the edit and the
arguments sent. The task detail page gains Retry with changes. -
Workflow tree for chains, groups and chords. Runs now record Celery's
parent_id,root_idand group id (schema revision0003; captured from
task events and from@track's request).GET /api/tasks/{id}/workflow
returns the whole tree (capped at 500 nodes), the task detail payload gains
parent_id/root_id/group_id, and the task detail page shows a
Workflow card: an indented, collapsible tree with a status summary. -
Workers page. A new Workers page lists every worker that answers the
broker: status, PID, uptime, pool, processed count, active/reserved/
scheduled counts (active tasks link to their run), and consumed queues. It
refreshes every 10 seconds while the tab is visible. From each worker card
you can shut the worker down (with a confirm step), add or remove a pool
process, and start or stop consuming a queue. Backed byGET /api/workers
andPOST /api/workers/{worker}/shutdown,.../pool/grow,
.../pool/shrink,.../queues/addand.../queues/cancel(also
celery_manager.control.workers). Every control is audited. If the broker
can't be reached the endpoints answer503with a readable message, not a
500. New config fieldinspect_timeout_seconds(default1.0) caps how
long each broadcast waits for replies. See Workers. -
Runtime rate limits.
POST /api/tasks/rate-limitchanges a task's rate
limit on running workers (all, or adestinationlist) through
control.rate_limit. The rate format (10/s,10/m,100/h, or""to
remove the limit) is checked before anything is sent, and the change is
audited.GET /api/workers/rate-limitslists registered tasks with the
limit declared in code. On the Workers page a Rate limits table lets you
edit a limit inline and shows which workers confirmed it. These limits are
not persisted: a worker that restarts goes back to the limit declared
in code. -
Queue depth.
GET /api/queueslists every queue celery-manager knows
of (default,task_queues,task_routes, queues seen on runs, and
queues live workers consume). For each one it gives the broker's message
count and consumer count, read with a passivequeue_declare, next to the
number of runs recorded asqueuedthere. It always answers200: an
unreachable or slow broker (3s deadline) or amemory://broker gives
depth: nulland a reason. A new Queues page in the dashboard shows
this, refreshes every 5 seconds (paused while the tab is hidden) and draws
a sparkline of depth over the session. See Queues. -
Purge a queue.
POST /api/queues/{name}/purgeremoves the ready
messages from one queue and returns how many. If it removed any, runs
recorded asqueuedin that queue without an ETA are markedrevoked,
since no event will ever arrive for them. Audited aspurgewith both
counts. In the dashboard you confirm by typing the queue name. -
Bulk cancel.
POST /api/tasks/cancel-bulkrevokes a list of
task_ids, or everyqueued/started/retriedrun matching the task
list's filters. It is capped at 1,000 (above that it's refused, not cut
short), has adry_runcount and an opt-interminate, and writes one
cancel_bulkaudit row. The task list gains Cancel N selected on the
Queued, Started and Retried tabs, and Cancel all matching… with the
count shown before you confirm. -
Pause a schedule until a set time.
POST /api/schedules/{name}/pause
takes an optional{"until"}(future, UTC; past is a422) and the
schedule resumes by itself afterwards, with no background job: readers
clear the stale pause lazily and write one automaticresume_schedule
audit row. Re-pausing with a newuntilupdates it.paused_untiljoins
the schedule payloads, and each occurrence'spausedis now per occurrence.
The dashboard's pause button becomes a menu (indefinitely, 1 hour, until
tomorrow 9:00, custom time). Schema revision0004.
Changed
- Dashboard redesign. shadcn/ui-style look (zinc theme, Geist fonts) in
light and dark. The sidebar collapses to icons (Ctrl/Cmd+B, remembered per
browser) and slides in over the page on phones. The task list gains a run
strip (one bar per run, coloured by status), status tabs and a search and
filter bar. Task detail puts the failure and logs first with execution
details alongside. Tables stack on narrow screens. - Schema migrations with Alembic. celery-manager's tables are now
versioned, and the migrations ship inside the package (noalembic.ini
needed). By defaultinit_db()upgrades the database at startup, under a
lock so the API, workers and listener can start together. Databases from
0.1.0a1–0.1.0a3 are adopted automatically and keep their data. The new
auto_migrateconfig field (defaultTrue) turns this off for teams that
migrate as a deploy step with the newcelery-manager migrate --db-url ...
command (--checkreports status and exits 1 if an upgrade ...
celery-manager 0.1.0a3
Third alpha of celery-manager: stable interval-schedule skips and race-free task-run writes (verified against real PostgreSQL and MySQL).
pip install --pre -U celery-managerFixed
- Interval-schedule occurrences (
run_every/ seconds /timedelta) are
now anchored to the schedule's last observed run instead of "now + interval".
Skips on interval schedules display stably across page loads and actually
fire against beat's real run times. - Race-free
task_runswrites. The events listener, the@track
decorator,report_progress()and the retry button used to check-then-insert
the same row. They now share one race-safe get-or-create (native upsert on
SQLite/PostgreSQL/MySQL, SAVEPOINT fallback elsewhere), so:- retry no longer returns a 500 or loses its lineage when the worker's row
lands first; - the listener no longer leaves
worker/queueempty when it loses the race; - late or out-of-order events can no longer downgrade a run's status;
- the producer's hostname from
task-sentis no longer recorded as the worker; - event arguments are stored as JSON, so retries from listener-created rows
decode correctly.
- retry no longer returns a 500 or loses its lineage when the worker's row
Added
- Documentation site: https://pythondeveloper6.github.io/celery-manager/.
- Dashboard screenshots in the README and docs, and an "Interval schedules"
section in the dashboard guide. - The race tests run against PostgreSQL in CI (
CM_TEST_DB_URL).
Changed
- README and docs no longer say tasks can be started from the UI; starting is
API-only (POST /api/tasks/start).
celery-manager 0.1.0a2
First PyPI release.
pip install --pre celery-manager
No code changes since 0.1.0a1 apart from the version; README and package metadata made PyPI-ready. Full notes: CHANGELOG.
celery-manager 0.1.0a1 (alpha 1)
First alpha of celery-manager: a dashboard and REST API for monitoring and controlling Celery tasks, with task history, live progress, retries, schedules and pause/resume.
This is an early pre-release; expect rough edges and read the Breaking and Known limitations sections below.
Install
pip install "git+https://github.com/Pythondeveloper6/celery-manager.git@v0.1.0a1"
Or download the wheel below and pip install celery_manager-0.1.0a1-py3-none-any.whl.
Changelog
The initial release. Before this alpha, celery-manager had never been published, so everything
below is new rather than changed — with a few exceptions, called out first
because anyone who pinned a git SHA during development will hit them.
Breaking
-
GET /api/tasksnow returns an envelope, not a bare array. It used to
return a JSON array of runs, hard-capped at 50 rows, with nooffset— so
older history was unreachable and a client could not tell "50 results" from
"the first 50 of 1,284". It now returns:{"items": [ ... ], "total": 1284, "limit": 50, "offset": 0}Callers must change
res.json()tores.json().items. The items also
gainedruntime_secondsand the fourprogress_*fields. There is no
compatibility shim and no version negotiation: the change was taken
deliberately while the package was unreleased and its only consumers were
the bundled dashboard anddocs/rest-api.md. See
the REST API reference. -
The session cookie is now
cm_session, andcreate_app()refuses a
defaultsecret_key. Both are security fixes; see Security
below for what changed and what you need to do.
Added
Task history, search and filtering
GET /api/tasksgainedq,date_from,date_to,limitandoffset
alongside the existingstatusandtask_name.limitis clamped into
1..500(control.tasks.MAX_PAGE_SIZE) rather than rejected, and the
clamped values are echoed back in the envelope.qis a case-insensitive substring search OR-ed acrosstask_name,
task_id,argsandkwargs. Because args/kwargs are stored as
serialized JSON text, this searches that text — there is no way to express
kwargs.user == "alice". LIKE wildcards in the search term are escaped, so
q=100%finds a literal100%instead of matching every row.- Both date bounds apply to
started_atand are inclusive, which means a
queued run (nostarted_at) is excluded by either bound, anddate_tois
an instant rather than a calendar day. control.count_task_runs()— the total behind "showing 50 of 1,284". It
shares one filter builder withlist_task_runs(), so a total can never be
a count of a different query than the page it labels.
Live progress
celery_manager.report_progress(current, total=None, message=None), called
from inside a task body. Writesprogress_current,progress_total,
progress_messageandprogress_updated_aton the run'stask_runsrow
while the task is still in flight; all four are served raw by both task
endpoints so the client decides how to render them.- Database writes are throttled to one per 0.5 s per run
(progress.MIN_WRITE_INTERVAL_SECONDS), so it is safe in a tight loop. The
completing call (current >= total) bypasses the throttle, so a finished
task never sits at 99%. - Omitting
totalmeans open-ended progress: a count and a message, no
percentage. Atotalof 0, negative, or non-int-able is treated the same
way — celery-manager never invents a denominator. - Never raises into the calling task, and never blocks it: bad arguments are
ignored, a missing task context or an unwired process each log one WARNING
per process and return. - Works on undecorated tasks too, via
celery.current_task.
Retry lineage
task_runs.retried_from_id, stamped byretry_task()on the run it
dispatches, so the chain is visible immediately rather than whenever the
events listener catches up.control.list_retries_of()and theretried_from_id/retried_byfields
onGET /api/tasks/{task_id}.retried_byis a list — retrying the same
failed run more than once is normal, and each attempt is its own run.
Branding settings
GET/PUT /api/settingsand theapp_settingstable, holding exactly
three keys:brand_name,brand_accent,brand_logo_url. Deployment-wide,
not per-browser.GET /api/settingsis deliberately unauthenticated — the login screen
has to render the deployment's branding, and the login screen is pre-auth.
That is safe only becauseDEFAULT_SETTINGSis a closed allowlist of
non-sensitive values;updated_by/updated_atare deliberately not
returned. Treat adding a key to that dict as a security decision.- Values are validated strictly and rejected with a
422carrying a
human-readable reason: the accent is an anchored hex allowlist (it lands in
a CSS custom property), and the logo URL is a scheme allowlist that refuses
SVG data URIs (it lands in an<img src>). - Any authenticated user can rebrand — there is no role system in v1 — but
every changed key writes an audit row with old and new values. control.get_settings()/control.update_settings()for the same
operations from Python.
Task detail depth
GET /api/tasks/{task_id}gainedqueue,worker,eta,started_at,
finished_at,runtime_secondsand the fourprogress_*fields. (eta
is served but nothing currently writes it — see the note in the REST API
reference.)
Dashboard
- Task list rebuilt around a left filter panel (search, status, group-by,
collapsible date range) with removable active-filter chips, repeated in the
empty state so an empty table says which filter emptied it. The panel
collapses behind a Filters toggle below ~1024px. - Two explicit modes: live (polls every 5 s, Load more widens the
page up to the 500-row ceiling) and history (explicit offset paging
through the full filtered total, polling switched off so rows can't shift
under you between clicks), with a Back to live bar. - Per-row progress bars with percentage, message and a projected time
remaining; a run that has stopped reporting says how long it has been quiet
and drops the estimate rather than counting down as if nothing were wrong. - Task detail gained a Run panel (progress, live elapsed, estimated finish),
an Execution panel (worker, queue, ETA, timings, each with a reason rather
than a blank cell), and a retry-lineage panel walking the chain in both
directions. - Light / dark / system theming as an explicit per-browser choice, stored
in local storage, applied before first paint and synced across tabs. - A branding settings screen with a live preview and a measured contrast
readout for the accent colour. - Dashboard: sign-out control in the nav.
- Dashboard: Cancel and Terminate controls on the task detail page (terminate asks for inline confirmation)
Framework integration
- Mount the dashboard under a prefix inside a FastAPI app
(app.mount("/celery-manager", dashboard)) or a Flask app (viaa2wsgi
and werkzeug'sDispatcherMiddleware, newflaskextra). - The SPA now uses relative asset and API URLs, so it works under any mount
prefix. - Runnable
examples/fastapi_demoandexamples/flask_demo. - New docs page
docs/integrations.md.
Packaging and docs
- MkDocs Material documentation site under
docs/. - Root
README.md, PyPI classifiers, keywords and project URLs;uvicorn
moved into astandaloneextra since mounting the ASGI app into a server
you already run needs no server of its own. - MIT license.
Changed
@tracknow writes astartedrow before calling your task body, not
only a terminal row afterwards. This is what makes an in-flight task
visible with no events-listener process running, and what gives
report_progress()a row to write to. Cost: one extra INSERT/UPDATE and
commit per tracked run. As with every other write in the decorator, a
failure is logged and dropped and can never change the task's outcome.POST /api/schedules/{name}/occurrences/pausenow rejects
action="reschedule"with a422. It used to accept and store it, which
displayed the occurrence as paused while doing nothing — the decorator only
ever acted onskip/cancel. The column still permits the value; the
endpoint no longer does.- The session cookie is now
Secureby default (cookie_secure=True). Set
it toFalseonly for local plain-HTTP development, where otherwise you
will log in successfully and then immediately appear logged out. beat_scheduleentries are normalized through
celery.schedules.maybe_schedule(), so a bare number of seconds or a
timedeltaworks the same as an explicitschedule()/crontab().self.retry()is now recorded asretriedrather thanfailed. It raises
celery.exceptions.Retry, an ordinaryExceptionsubclass, which used to
land in the decorator's failure branch and give a run Celery considers
RETRY afailedstatus, a "Retry in 30s: …" exception message and a finish
time it did not have.- A retry attempt now clears what the previous attempt left on the shared row
(exception,traceback,finished_at,runtime_seconds, the four
progress_*columns). A task that failed once and then succeeded used to
render assucceededwith a stale traceback and a stale progress bar. POST /api/tasks/{id}/retryreturns404for an unknown task id instead of
an unhandledValueError(a500);retry-bulkdoes the same.
control.TaskRunNotFoundis aValueErrorsubclass, so Python callers
already catchingValueErrorare unaffected.
Fixed
- The events listener no longer downgrades a decorator-owned
skippedstatus.
A skipped occurrence returns normally, so the worker emits an ordinary
task-succeededfor ...
