Workflows¶
WorkflowEngine is an in-process background job queue with retries plus a recurring-task scheduler — no separate broker (Redis, RabbitMQ) required.
Setup¶
from shakti import WorkflowEngine
workflows = WorkflowEngine(workers=4, max_retries=3, prefix="/jobs")
workflows.init_app(app)
init_app registers WorkflowEngine in the DI container, mounts the job-management routes under prefix (default /jobs), and starts/stops the worker pool and scheduler on app startup/shutdown.
Background jobs¶
@workflows.job
async def send_email(to: str, subject: str, body: str) -> str:
# ... send email ...
return f"Email sent to {to}"
@app.post("/register")
async def register(body: dict) -> dict:
user = await create_user(body)
await workflows.enqueue(send_email, to=user["email"], subject="Welcome!", body="Thanks for joining.")
return {"user": user}
@workflows.job just marks a function so it shows up in workflows.stats()'s registered_jobs count — you don't have to decorate a function to enqueue it, but doing so gives you a single place that lists everything schedulable.
enqueue(func, *, name=None, max_retries=None, delay_seconds=0, **kwargs) runs func(**kwargs) on a worker as soon as one is free (or after delay_seconds), retrying up to max_retries times (default: the engine's max_retries) on failure, and returns a Job immediately so you can track it:
job = await workflows.enqueue(send_email, to="a@b.com", subject="Hi", body="...")
job.id # str — use with workflows.status(job.id)
Job status¶
job = workflows.status(job_id) # raises HTTPException(404) if unknown
job.status # JobStatus: pending | running | completed | failed | retrying | cancelled
job.result # return value, once completed
job.error # error message, if failed
job.retries # attempts so far
Scheduled (recurring) jobs¶
@workflows.every(minutes=30)
async def cleanup_sessions() -> None:
...
@workflows.every(hours=24)
async def daily_report() -> None:
...
every(seconds=, minutes=, hours=, days=, name=None) — combine units freely (they're summed into one interval); the function runs on that fixed cadence for the lifetime of the app.
Job-management routes¶
Mounted under prefix (default /jobs):
| Route | Does |
|---|---|
GET /jobs/ |
last 50 jobs |
GET /jobs/{job_id} |
one job's status |
DELETE /jobs/{job_id} |
cancel (only if still pending) |
POST /jobs/{job_id}/retry |
re-queue a failed/cancelled job |
GET /jobs/scheduled |
list of recurring jobs |
GET /jobs/stats |
queue + scheduler summary |
Scope and limits¶
The queue and scheduler are in-process and in-memory — jobs and their history don't survive a restart, and state isn't shared across multiple worker processes. This is a good fit for a single-process deployment doing moderate background work (emails, cleanup tasks, report generation); for durable, multi-process, or high-volume job processing, put a real broker (Redis/Celery/RQ, etc.) behind your own integration instead.