Skip to content

Getting started

Install

pip install --pre gylo

The wheel carries both the Python package and the gylo binary — worker, migrations, and operations tooling in one install. msgspec is the only runtime dependency; your database driver is yours to choose:

pip install --pre "gylo[asyncpg]"    # or [psycopg]

A database and its schema

gylo needs PostgreSQL 14+. Apply the schema with the bundled binary:

export DATABASE_URL=postgres://user:pass@localhost/myapp
gylo migrate

Migrations are embedded in the binary, additive, and safe to re-run.

Define a task

myapp.py
import gylo

app = gylo.Gylo()


@app.task
async def send_receipt(order_id: int, *, email: str) -> None:
    print(f"receipt for order {order_id} -> {email}")

A task is a plain function with a decorator. It stays callable directly — await send_receipt(1, email="a@b.c") runs it inline, which is how you unit test it.

Enqueue — inside your own transaction

async with pool.acquire() as conn, conn.transaction():
    order_id = await create_order(conn, ...)
    await send_receipt.enqueue(conn, order_id, email="a@b.c")

The connection is explicit and the point: the job commits atomically with the order. Roll back, and the job was never enqueued. From synchronous code — a Django view, a Flask handler — use send_receipt.enqueue_sync(conn, ...); see Synchronous code.

Enqueue is fully typed: send_receipt.enqueue(conn, "one", emial=...) fails your type checker with the task's real signature in the error.

Run a worker

gylo worker --app myapp:app

That is the whole deployment: one process that spawns a Python child per core, leases jobs in batches, and finalises them durably. Ctrl-C or SIGTERM drains in-flight work before exiting.

See it

gylo queue            # depth per queue: ready, scheduled, blocked, running
gylo jobs failed      # dead-lettered jobs with their last error

From here: Tasks and options for the full API, or Deployment before anything faces traffic.