Coming from TaskIQ
TaskIQ is a toolkit — you choose a broker, a result backend, a scheduler and middlewares, and wire them together. gylo is the assembled machine: Postgres is the broker and the result store, the scheduler runs inside the worker, and the middleware jobs are built in.
The structural difference
A TaskIQ deployment composes several moving parts:
broker = ListQueueBroker("redis://…").with_result_backend(
RedisAsyncResultBackend("redis://…")
)
scheduler = TaskiqScheduler(broker, [LabelScheduleSource(broker)])
gylo has one part, and enqueueing takes the database connection you already hold, so the job commits with your data:
import gylo
app = gylo.Gylo()
@app.task
async def resize(image_id: int) -> None: ...
async def upload(conn, image_id: int) -> None:
async with conn.transaction():
await mark_uploaded(conn, image_id)
await resize.enqueue(conn, image_id)
No separate scheduler process, no result backend to pick, no broker/worker version skew — and no enqueue that succeeds while the transaction that motivated it rolls back.
Concept map
| TaskIQ | gylo |
|---|---|
broker + result_backend |
gylo.Gylo() — Postgres is both |
@broker.task |
@app.task |
await my_task.kiq(x) |
await task.enqueue(conn, x) |
.kicker().with_labels(…) |
.options(queue=…, priority=…, delay=…, …) |
schedule=[{"cron": "*/5 * * * *"}] label + scheduler process |
@app.cron("*/5 * * * *"), inside the worker |
await handle.wait_result(timeout=…) |
poll await gylo.outcome(conn, job_id) |
NoResultError / opt-out of results |
results are opt-in: store_result=True |
retry middleware (SimpleRetryMiddleware) |
built in — exponential backoff with jitter, retry_on / no_retry_on |
TaskiqDepends |
nothing — see below |
InMemoryBroker for tests |
not yet; today tests run against a real database |
| taskiq-pipelines | gylo.chain / group / chord — no result piping |
What gylo will not do the same way
No dependency injection. TaskiqDepends is TaskIQ's marquee feature —
FastAPI-style parameters resolved at execution. gylo tasks are plain
functions: they take arguments from the enqueue and reach shared resources
(pools, clients) as module state. If your task graph leans heavily on DI,
this is the largest rewrite item — and the part of TaskIQ gylo deliberately
does not copy, because implicit parameters are the part that resists typing
and testing. Framework integration helpers are a known gap.
No pluggable brokers or backends. TaskIQ's strength is choice — NATS, RabbitMQ, Redis, Kafka. gylo's position is that a task queue's reliability is its storage, so it ships Postgres and makes it excellent. If you need a specific broker for organisational reasons, TaskIQ is genuinely the better fit.
No blocking result wait. wait_result polls under the hood; gylo asks
you to poll outcome yourself so the cost is visible — and results expire
with the completed-job retention window (24h default). See
Results and cancellation.
No middleware API. Custom middlewares have no seam to attach to. The built-ins cover retries, timeouts, dead letters, metrics and graceful shutdown; anything beyond that is a feature request, not a plugin today.
Cron does not backfill. A schedule that came due while the worker fleet was down advances to its next occurrence — documented in Scheduling, stated here because separate-scheduler setups sometimes queue missed runs on restart.