Skip to main content

SQLite-backed task queue (stdlib only, no dependencies)

Project description

liteworks
=========

SQLite-backed task queue for Python 3.12+. Zero dependencies; uses Python's
stdlib HTTP server. Each queue is one SQLite file. Workers on multiple machines
connect over HTTP. No external service required.


ARCHITECTURE
------------

Uses a visibility timeout. The atomic claim is one SQL statement:

UPDATE jobs SET claimed_by = ?, claimed_at = ?
WHERE id = (
SELECT id FROM jobs
WHERE (claimed_by IS NULL OR claimed_at < ?) AND scheduled_at <= ?
ORDER BY scheduled_at LIMIT 1
) RETURNING *

Ack uses a fencing token so a timed-out worker cannot clobber a fresh claim:

DELETE FROM jobs WHERE id = ? AND claimed_by = ? RETURNING *

Ack + DLQ write are atomic (one SQL transaction via POST /{queue}/fail).
Snooze clears the claim and advances scheduled_at.
Retry schedules a future re-delivery by advancing scheduled_at and
incrementing delivery_count.

One server, multiple queues: each queue maps to {data_dir}/{queue}.db and
is created lazily on first use.

One handler per message type; fanout belongs in the handler.


INSTALL
-------

pip install liteworks


QUICKSTART
----------

Start the server:

liteworks serve --data-dir /var/liteworks --port 8000

In your application:

from dataclasses import dataclass
from liteworks import Liteworks, RetryPolicy, Snooze, Discard

lw = Liteworks(base_url='http://localhost:8000', queue='emails')

@dataclass
class SendEmail:
to: str
subject: str

@lw.handler
def send_email(msg: SendEmail):
# raise Snooze(60) to defer and retry in 60s (delivery_count unchanged)
# raise Discard('bad_address') to move straight to DLQ
pass

lw.publish(SendEmail(to='x@example.com', subject='Hello'))

w = lw.worker()
w.install_signal_handlers()
w.run()


MULTIPLE QUEUES
---------------

One server hosts all queues. Each queue name maps to its own SQLite file.

emails = Liteworks(base_url='http://localhost:8000', queue='emails')
reports = Liteworks(base_url='http://localhost:8000', queue='reports')

emails.publish(SendEmail(...))
reports.publish(GenerateReport(...))

Queue names: [a-zA-Z0-9_-], 1-64 characters.


DEMO
----

See demo.py for a runnable two-queue example with workers, stats, and a
browser dashboard. Run:

python demo.py


STATS AND UI
------------

GET /{queue}/stats returns JSON:

{
"queue": "emails",
"ready": 3,
"claimed": 1,
"scheduled": 0,
"failed": 2,
"completed": 47,
"recent_failures": [
{"id": "...", "reason": "SMTP timeout", "failed_at": 1720000000.0}
],
"recent_runs": [
{"id": "...", "worker_id": "w1", "completed_at": 1720000001.0}
]
}

lw.stats() # via Transport; returns same dict

GET / returns a top-level dashboard (self-contained HTML, auto-refresh) showing
all queues and an enqueue form. Open http://localhost:8000/ in a browser.

GET /queues returns a JSON list of queue names.
GET /metrics returns Prometheus-format text metrics for all queues.


AUTH
----

Protect with a shared secret:

liteworks serve --data-dir /var/liteworks --secret $LITEWORKS_SECRET

lw = Liteworks(base_url='...', queue='emails', secret='...')

GET / is always accessible (browser loads the page before it can send auth).
GET /queues, GET /metrics, GET /{queue}/stats require Basic auth when secret set.
POST, DELETE, PATCH accept either HMAC-SHA256 x-signature (API clients) or
Basic auth (browser). Basic auth password is the secret; username is ignored.

Completed job retention (default: keep last 1000 per queue; 0 = disabled):

liteworks serve --data-dir /var/liteworks --completed-retention 500

lw = Liteworks(..., completed_retention=500)


RETRY POLICY
------------

Default: 3 retries, exponential backoff, 5s base, 300s cap, jitter on.

from liteworks import RetryPolicy

@lw.handler(policy=RetryPolicy(max_retries=5, base_delay=2.0, jitter=False))
def handle(msg: MyMsg): ...


PUBLISH OPTIONS
---------------

lw.publish(msg, msg_id='my-uuid') # idempotency key
lw.publish(msg, parent_id='parent-job-id') # lineage
lw.publish(msg, scheduled_at=time.time()+3600) # deferred delivery


SCHEMA
------

Created automatically on first use of each queue:

CREATE TABLE IF NOT EXISTS jobs (
id TEXT PRIMARY KEY,
payload TEXT NOT NULL,
msg_id TEXT,
parent_id TEXT,
delivery_count INTEGER DEFAULT 0,
scheduled_at REAL NOT NULL,
claimed_by TEXT,
claimed_at REAL,
created_at REAL NOT NULL
);

CREATE TABLE IF NOT EXISTS failed_jobs (
id TEXT PRIMARY KEY,
payload TEXT NOT NULL,
msg_id TEXT,
parent_id TEXT,
reason TEXT,
failed_at REAL NOT NULL
);

CREATE TABLE IF NOT EXISTS completed_jobs (
id TEXT PRIMARY KEY,
payload TEXT NOT NULL,
msg_id TEXT,
parent_id TEXT,
delivery_count INTEGER DEFAULT 0,
worker_id TEXT,
completed_at REAL NOT NULL
);

msg_id has a partial unique index (WHERE msg_id IS NOT NULL) enforcing
idempotent publish when the caller passes an explicit msg_id.
completed_jobs retains the last N entries per queue (trimmed on each ack),
where N is the completed_retention setting (default 1000; 0 disables tracking).


DURABILITY
----------

Use Litestream to replicate each SQLite file to S3:

litestream replicate /var/liteworks/emails.db s3://bucket/emails.db


PUBLIC API
----------

serve(data_dir, host='', port=8000, secret=None) module-level, blocking

Liteworks(data_dir=None, queue='default',
base_url='http://localhost:8000',
visibility_timeout=30.0, secret=None)
Liteworks.handler(fn=None, *, policy=None) decorator / decorator factory
Liteworks.publish(msg, *, msg_id=None, parent_id=None, scheduled_at=None)
Liteworks.stats() -> dict
Liteworks.worker(*, poll_interval=0.5, backoff_interval=2.0, max_empty=4)
Liteworks.serve(port=8000, host='') blocking; requires data_dir

Worker.run()
Worker.stop()
Worker.install_signal_handlers()

Snooze(seconds) raise from handler to defer without consuming a retry
Discard(reason) raise from handler to skip retries and go to DLQ

RetryPolicy(max_retries=3, backoff='exponential', base_delay=5.0,
max_delay=300.0, jitter=True)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

liteworks-0.1.0.tar.gz (37.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

liteworks-0.1.0-py3-none-any.whl (24.3 kB view details)

Uploaded Python 3

File details

Details for the file liteworks-0.1.0.tar.gz.

File metadata

  • Download URL: liteworks-0.1.0.tar.gz
  • Upload date:
  • Size: 37.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for liteworks-0.1.0.tar.gz
Algorithm Hash digest
SHA256 9d52f9a224c79d6e27326f05afc763fa66449bb577890233af78050b74372707
MD5 15282256d7534b93d867ac7abe4afcd0
BLAKE2b-256 2fd3d0886483315d1dba8b9ac16b02a32e3c60d2f2097f2b23dd7c77f829ed45

See more details on using hashes here.

File details

Details for the file liteworks-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: liteworks-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 24.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for liteworks-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 75893d24ae4f09cfd3e723817aced6139f581f726cb6b87da14787bf3120613b
MD5 fda9936db9262e6e6a6c1b6bfd6ca35f
BLAKE2b-256 49a505a38402233161f4ad21399c8c8c5da6a261082719f19b4742590be19943

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page