localqueue 0.4.0__tar.gz → 0.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {localqueue-0.4.0 → localqueue-0.4.1}/CHANGELOG.md +10 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/PKG-INFO +14 -3
- {localqueue-0.4.0 → localqueue-0.4.1}/README.md +13 -2
- {localqueue-0.4.0 → localqueue-0.4.1}/docs/index.md +15 -5
- {localqueue-0.4.0 → localqueue-0.4.1}/docs/queues.md +19 -4
- {localqueue-0.4.0 → localqueue-0.4.1}/docs/retries.md +21 -8
- localqueue-0.4.1/docs/use-cases/local-outbox.md +83 -0
- localqueue-0.4.1/docs/use-cases/operator-recovery.md +85 -0
- localqueue-0.4.1/docs/use-cases/persistent-retries.md +74 -0
- localqueue-0.4.1/docs/use-cases.md +54 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/pyproject.toml +1 -1
- {localqueue-0.4.0 → localqueue-0.4.1}/uv.lock +1 -1
- localqueue-0.4.0/docs/use-cases.md +0 -174
- {localqueue-0.4.0 → localqueue-0.4.1}/.gitignore +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/LICENSE +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/docs/api.md +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/docs/compare.md +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/docs/develop.md +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/docs/operational-maturity.md +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/docs/release.md +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/docs/stability.md +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/examples/email_worker.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/examples/enqueue_email.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/examples/process_webhook.sh +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/examples/retry_demo.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/examples/sqlite_concurrency_benchmark.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/examples/sqlite_process_harness.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/localqueue/__init__.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/localqueue/cli.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/localqueue/cli_commands.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/localqueue/cli_support.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/localqueue/cli_worker_commands.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/localqueue/failure.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/localqueue/paths.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/localqueue/queue.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/localqueue/retry/__init__.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/localqueue/retry/stores/__init__.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/localqueue/retry/stores/_shared.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/localqueue/retry/stores/base.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/localqueue/retry/stores/lmdb.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/localqueue/retry/stores/memory.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/localqueue/retry/stores/sqlite.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/localqueue/retry/tenacity.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/localqueue/services/__init__.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/localqueue/services/queue_worker.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/localqueue/stores/__init__.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/localqueue/stores/_shared.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/localqueue/stores/base.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/localqueue/stores/lmdb.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/localqueue/stores/memory.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/localqueue/stores/sqlite.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/localqueue/worker.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/tests/test_cli.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/tests/test_process_harness.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/tests/test_queue.py +0 -0
- {localqueue-0.4.0 → localqueue-0.4.1}/tests/test_retry.py +0 -0
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.1](https://github.com/brunoportis/localqueue/compare/v0.4.0...v0.4.1) (2026-04-25)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Documentation
|
|
7
|
+
|
|
8
|
+
* reposition use cases and guides ([8e98379](https://github.com/brunoportis/localqueue/commit/8e98379c29091b414ad437facbdb4d7a7c45db4c))
|
|
9
|
+
* reposition use cases and guides ([280288b](https://github.com/brunoportis/localqueue/commit/280288b02f877d78d2df067df67ce14a800cbfe2))
|
|
10
|
+
* simplify queue examples ([ed488b2](https://github.com/brunoportis/localqueue/commit/ed488b2ab210369331fbcda3568760f0da24618b))
|
|
11
|
+
* split use cases into dedicated pages ([5496826](https://github.com/brunoportis/localqueue/commit/54968266b0733b009147c56761ce9fe29889d5e9))
|
|
12
|
+
|
|
3
13
|
## [0.4.0](https://github.com/brunoportis/localqueue/compare/v0.3.4...v0.4.0) (2026-04-24)
|
|
4
14
|
|
|
5
15
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: localqueue
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Durable local queues for Python, with persistent retry state powered by Tenacity.
|
|
5
5
|
Project-URL: Homepage, https://brunoportis.github.io/localqueue/
|
|
6
6
|
Project-URL: Documentation, https://brunoportis.github.io/localqueue/
|
|
@@ -40,9 +40,17 @@ Description-Content-Type: text/markdown
|
|
|
40
40
|
[](https://pypi.org/project/localqueue/)
|
|
41
41
|

|
|
42
42
|
|
|
43
|
-
`localqueue` is a small durable queue for one machine. It stores work on the
|
|
43
|
+
`localqueue` is a small durable queue for one machine. It stores work on the
|
|
44
|
+
local filesystem by default and keeps retry state with Tenacity.
|
|
44
45
|
|
|
45
|
-
Use it
|
|
46
|
+
Use it when the work can stay local and you want one of these outcomes:
|
|
47
|
+
|
|
48
|
+
- accept work now and perform the side effect later on the same machine
|
|
49
|
+
- inspect failed jobs and replay them from the terminal
|
|
50
|
+
- keep retry budgets across process restarts without adding a broker
|
|
51
|
+
|
|
52
|
+
It fits scripts, CLI tools, cron jobs, and small Python workers that share one
|
|
53
|
+
local store.
|
|
46
54
|
|
|
47
55
|
```bash
|
|
48
56
|
localqueue queue exec emails -- python scripts/send_email.py
|
|
@@ -61,6 +69,9 @@ def send_email(job: dict[str, str]) -> None:
|
|
|
61
69
|
|
|
62
70
|
Full docs live at [brunoportis.github.io/localqueue](https://brunoportis.github.io/localqueue/). The source docs are in [`docs/`](docs/).
|
|
63
71
|
|
|
72
|
+
Start with [`docs/use-cases.md`](docs/use-cases.md) if you want the shortest
|
|
73
|
+
path to deciding whether the project fits your workflow.
|
|
74
|
+
|
|
64
75
|
For development and release workflow details, see [`docs/develop.md`](docs/develop.md).
|
|
65
76
|
|
|
66
77
|
## Install
|
|
@@ -5,9 +5,17 @@
|
|
|
5
5
|
[](https://pypi.org/project/localqueue/)
|
|
6
6
|

|
|
7
7
|
|
|
8
|
-
`localqueue` is a small durable queue for one machine. It stores work on the
|
|
8
|
+
`localqueue` is a small durable queue for one machine. It stores work on the
|
|
9
|
+
local filesystem by default and keeps retry state with Tenacity.
|
|
9
10
|
|
|
10
|
-
Use it
|
|
11
|
+
Use it when the work can stay local and you want one of these outcomes:
|
|
12
|
+
|
|
13
|
+
- accept work now and perform the side effect later on the same machine
|
|
14
|
+
- inspect failed jobs and replay them from the terminal
|
|
15
|
+
- keep retry budgets across process restarts without adding a broker
|
|
16
|
+
|
|
17
|
+
It fits scripts, CLI tools, cron jobs, and small Python workers that share one
|
|
18
|
+
local store.
|
|
11
19
|
|
|
12
20
|
```bash
|
|
13
21
|
localqueue queue exec emails -- python scripts/send_email.py
|
|
@@ -26,6 +34,9 @@ def send_email(job: dict[str, str]) -> None:
|
|
|
26
34
|
|
|
27
35
|
Full docs live at [brunoportis.github.io/localqueue](https://brunoportis.github.io/localqueue/). The source docs are in [`docs/`](docs/).
|
|
28
36
|
|
|
37
|
+
Start with [`docs/use-cases.md`](docs/use-cases.md) if you want the shortest
|
|
38
|
+
path to deciding whether the project fits your workflow.
|
|
39
|
+
|
|
29
40
|
For development and release workflow details, see [`docs/develop.md`](docs/develop.md).
|
|
30
41
|
|
|
31
42
|
## Install
|
|
@@ -4,19 +4,30 @@ icon: lucide/inbox
|
|
|
4
4
|
|
|
5
5
|
# Overview
|
|
6
6
|
|
|
7
|
-
`localqueue` is a small durable queue for one machine. It stores work on the
|
|
7
|
+
`localqueue` is a small durable queue for one machine. It stores work on the
|
|
8
|
+
local filesystem by default and keeps retry state with Tenacity.
|
|
8
9
|
|
|
9
|
-
It fits scripts, CLI tools, cron jobs, development helpers, and small workers
|
|
10
|
+
It fits scripts, CLI tools, cron jobs, development helpers, and small workers
|
|
11
|
+
that can safely share one local store. It is not a distributed broker and does
|
|
12
|
+
not provide multi-host coordination.
|
|
10
13
|
|
|
11
14
|
!!! note
|
|
12
15
|
The default model is local-file storage, at-least-once delivery, and best-effort ordering under concurrency.
|
|
13
16
|
|
|
17
|
+
## Best-fit workflows
|
|
18
|
+
|
|
19
|
+
`localqueue` is strongest in three situations:
|
|
20
|
+
|
|
21
|
+
- local outbox workflows where the caller should return before the side effect happens
|
|
22
|
+
- terminal-driven recovery where operators need dead-letter inspection and replay
|
|
23
|
+
- persistent retry budgets for work that already arrives from somewhere else
|
|
24
|
+
|
|
14
25
|
## CLI
|
|
15
26
|
|
|
16
27
|
Use the CLI when you want to enqueue, inspect, watch, and recover jobs from the terminal.
|
|
17
28
|
|
|
18
29
|
```bash
|
|
19
|
-
|
|
30
|
+
echo '{"to":"user@example.com"}' | localqueue queue add emails
|
|
20
31
|
localqueue queue exec emails -- python scripts/send_email.py
|
|
21
32
|
localqueue queue stats emails --watch --interval 1
|
|
22
33
|
localqueue queue dead emails --summary
|
|
@@ -42,14 +53,13 @@ def send_email(job: dict[str, str]) -> None:
|
|
|
42
53
|
deliver(job["to"])
|
|
43
54
|
```
|
|
44
55
|
|
|
45
|
-
`localqueue.retry` stays available when another system already delivers work and you only need retry state.
|
|
46
|
-
|
|
47
56
|
See [Persistent retries](retries.md) for the retry API and store options.
|
|
48
57
|
|
|
49
58
|
## When to use
|
|
50
59
|
|
|
51
60
|
- small Python workers on one machine
|
|
52
61
|
- scripts that need durable work after restarts
|
|
62
|
+
- local outbox workflows for emails, webhooks, uploads, or reports
|
|
53
63
|
- terminal-driven queues for jobs you want to inspect and requeue
|
|
54
64
|
- retry state that must survive process restarts
|
|
55
65
|
|
|
@@ -4,14 +4,28 @@ icon: lucide/inbox
|
|
|
4
4
|
|
|
5
5
|
# Persistent Queues
|
|
6
6
|
|
|
7
|
-
`localqueue`
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
`localqueue` queues are for local workflows where you want to accept work now,
|
|
8
|
+
run it later on the same machine, and keep enough state to inspect and recover
|
|
9
|
+
failures from the terminal.
|
|
10
|
+
|
|
11
|
+
This is the main workflow API in the project. It is a small queue abstraction
|
|
12
|
+
backed by SQLite by default and designed for durable, at-least-once delivery of
|
|
13
|
+
Python values.
|
|
10
14
|
|
|
11
15
|
It is local infrastructure: producers and consumers should run where they can
|
|
12
16
|
share the same queue store safely. It is not a distributed broker and does not
|
|
13
17
|
provide multi-host coordination.
|
|
14
18
|
|
|
19
|
+
Best-fit queue workflows:
|
|
20
|
+
|
|
21
|
+
- local outbox patterns for email, webhooks, uploads, or report generation
|
|
22
|
+
- CLI-driven workers that should survive restarts
|
|
23
|
+
- jobs that may need dead-letter inspection and replay
|
|
24
|
+
|
|
25
|
+
Start somewhere else if the queue itself needs to be a distributed system
|
|
26
|
+
boundary. `localqueue` keeps delivery local, recovery local, and operations
|
|
27
|
+
simple.
|
|
28
|
+
|
|
15
29
|
If you have independent workloads, give them separate queue names instead of
|
|
16
30
|
loading everything into one queue. Each queue keeps its own stats, dead
|
|
17
31
|
letters, retention settings, and worker identity history.
|
|
@@ -30,7 +44,8 @@ queue = PersistentQueue(
|
|
|
30
44
|
)
|
|
31
45
|
```
|
|
32
46
|
|
|
33
|
-
The queue name identifies an independent stream inside the store. Queue names
|
|
47
|
+
The queue name identifies an independent stream inside the store. Queue names
|
|
48
|
+
cannot be empty and cannot contain `:`.
|
|
34
49
|
Queue-level retry defaults are merged into worker retry kwargs before explicit
|
|
35
50
|
worker overrides, so the same policy can be reused across several workers that
|
|
36
51
|
consume the same queue.
|
|
@@ -4,14 +4,27 @@ icon: lucide/rotate-cw
|
|
|
4
4
|
|
|
5
5
|
# Persistent Retries
|
|
6
6
|
|
|
7
|
-
`localqueue` is
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
`localqueue.retry` is for code that already receives the work but still needs a
|
|
8
|
+
retry budget that survives process restarts.
|
|
9
|
+
|
|
10
|
+
This is the durable retry layer used by queue workers and by code that already
|
|
11
|
+
has its own delivery mechanism. It wraps Tenacity's `Retrying` and
|
|
12
|
+
`AsyncRetrying` classes. You still configure `stop`, `wait`, `retry`,
|
|
13
|
+
callbacks, and `retry_with()` the same way; the wrapper adds a durable attempt
|
|
14
|
+
record before each stop decision.
|
|
15
|
+
|
|
16
|
+
Best-fit retry workflows:
|
|
17
|
+
|
|
18
|
+
- cron jobs that call fragile external services
|
|
19
|
+
- HTTP handlers or consumers that need durable retry budgets per logical job
|
|
20
|
+
- scripts where retry state must outlive the current process
|
|
21
|
+
|
|
22
|
+
Use this layer when the missing piece is persistent attempt tracking. If you
|
|
23
|
+
need ack, release, leases, dead-letter records, or queue inspection, start with
|
|
24
|
+
[Persistent queues](queues.md) instead.
|
|
25
|
+
|
|
26
|
+
The main design constraint is stable identity: each logical job needs a stable
|
|
27
|
+
retry key so the same work does not retry forever under a new process.
|
|
15
28
|
|
|
16
29
|
## Decorator API
|
|
17
30
|
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
icon: lucide/mail
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Local Outbox
|
|
6
|
+
|
|
7
|
+
Use this pattern when the caller should return now and the external side effect
|
|
8
|
+
can happen a few seconds later on the same machine.
|
|
9
|
+
|
|
10
|
+
This is one of the strongest `localqueue` stories: accept work quickly, store
|
|
11
|
+
it durably, and let a local worker perform the slow or fragile side effect
|
|
12
|
+
later.
|
|
13
|
+
|
|
14
|
+
## Best fit
|
|
15
|
+
|
|
16
|
+
- email delivery from a web app or admin script
|
|
17
|
+
- webhook fan-out from a local integration process
|
|
18
|
+
- uploads, report generation, or notifications that should not block the caller
|
|
19
|
+
|
|
20
|
+
## Why `localqueue` fits
|
|
21
|
+
|
|
22
|
+
- no broker, Redis, or extra service to operate
|
|
23
|
+
- queue state survives process restarts
|
|
24
|
+
- the worker can be a Python function or an external command
|
|
25
|
+
|
|
26
|
+
## Boundaries
|
|
27
|
+
|
|
28
|
+
- this is a single-host pattern, not a distributed outbox
|
|
29
|
+
- delivery is at least once, so handlers should be idempotent
|
|
30
|
+
|
|
31
|
+
## Setup
|
|
32
|
+
|
|
33
|
+
Create a scratch directory and a small worker script once:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
mkdir -p /tmp/localqueue-use-cases
|
|
37
|
+
cat > /tmp/localqueue-use-cases/email_worker.py <<'PY'
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
|
|
40
|
+
import json
|
|
41
|
+
import sys
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
payload = json.load(sys.stdin)
|
|
45
|
+
address = payload["to"]
|
|
46
|
+
if payload.get("fail"):
|
|
47
|
+
raise ConnectionError(f"could not deliver email to {address}")
|
|
48
|
+
print(f"sent email to {address}")
|
|
49
|
+
PY
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Minimal flow
|
|
53
|
+
|
|
54
|
+
Enqueue one email job:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
echo '{"to":"user@example.com"}' | localqueue queue add emails
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Inspect the queue before processing:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
localqueue queue stats emails
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Process one queued message with the worker script:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
localqueue queue exec emails \
|
|
70
|
+
-- python /tmp/localqueue-use-cases/email_worker.py
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Check that the queue is empty again:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
localqueue queue stats emails
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## When to choose something else
|
|
80
|
+
|
|
81
|
+
Use a broker or managed queue instead when producers and consumers run on
|
|
82
|
+
different machines, strict global ordering matters, or the queue is part of a
|
|
83
|
+
distributed system boundary.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
icon: lucide/life-buoy
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Operator Recovery
|
|
6
|
+
|
|
7
|
+
Use this pattern when jobs can fail in recoverable ways and operators need to
|
|
8
|
+
inspect, dead-letter, and replay them from the terminal.
|
|
9
|
+
|
|
10
|
+
This is where `localqueue` feels more useful than a pile of shell scripts:
|
|
11
|
+
failures stay visible, recovery is explicit, and replay does not require a
|
|
12
|
+
custom one-off script.
|
|
13
|
+
|
|
14
|
+
## Best fit
|
|
15
|
+
|
|
16
|
+
- webhook delivery with occasional upstream outages
|
|
17
|
+
- notification pipelines that need human inspection on failure
|
|
18
|
+
- CLI-driven maintenance jobs where operators need a dead-letter queue
|
|
19
|
+
|
|
20
|
+
## Why `localqueue` fits
|
|
21
|
+
|
|
22
|
+
- failed jobs can move to dead-letter after a small retry budget
|
|
23
|
+
- operators can inspect summary and full records locally
|
|
24
|
+
- replay is one command instead of ad hoc recovery code
|
|
25
|
+
|
|
26
|
+
## Boundaries
|
|
27
|
+
|
|
28
|
+
- recovery is local to one machine and one shared store
|
|
29
|
+
- this does not replace broker-level operations across multiple services
|
|
30
|
+
|
|
31
|
+
## Setup
|
|
32
|
+
|
|
33
|
+
Create a scratch directory and a small worker script once:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
mkdir -p /tmp/localqueue-use-cases
|
|
37
|
+
cat > /tmp/localqueue-use-cases/email_worker.py <<'PY'
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
|
|
40
|
+
import json
|
|
41
|
+
import sys
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
payload = json.load(sys.stdin)
|
|
45
|
+
address = payload["to"]
|
|
46
|
+
if payload.get("fail"):
|
|
47
|
+
raise ConnectionError(f"could not deliver email to {address}")
|
|
48
|
+
print(f"sent email to {address}")
|
|
49
|
+
PY
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Minimal flow
|
|
53
|
+
|
|
54
|
+
Create a failing job:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
echo '{"to":"broken@example.com","fail":true}' | localqueue queue add webhooks
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Process it with a small retry budget so it reaches dead-letter quickly:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
localqueue queue exec webhooks \
|
|
64
|
+
--max-tries 2 \
|
|
65
|
+
-- python /tmp/localqueue-use-cases/email_worker.py
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Inspect the dead-letter summary and full record:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
localqueue queue dead webhooks --summary
|
|
72
|
+
|
|
73
|
+
localqueue queue dead webhooks
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Replay everything in the dead-letter queue:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
localqueue queue requeue-dead webhooks --all
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## When to choose something else
|
|
83
|
+
|
|
84
|
+
Use a different system when recovery, retention, and observability must work as
|
|
85
|
+
shared platform capabilities across multiple hosts or services.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
icon: lucide/rotate-cw
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Persistent Retries
|
|
6
|
+
|
|
7
|
+
Use this pattern when another part of your system already delivers the work and
|
|
8
|
+
the missing piece is a retry budget that survives process restarts.
|
|
9
|
+
|
|
10
|
+
This page is about persistent attempt tracking, not a full queue lifecycle. If
|
|
11
|
+
you need ack, release, leases, or dead-letter handling, start with
|
|
12
|
+
`PersistentQueue` instead.
|
|
13
|
+
|
|
14
|
+
## Best fit
|
|
15
|
+
|
|
16
|
+
- cron jobs that call an external API
|
|
17
|
+
- HTTP handlers that already receive the work but need durable retry budgets
|
|
18
|
+
- consumers from another queueing system that want persistent retry state
|
|
19
|
+
|
|
20
|
+
## Why `localqueue` fits
|
|
21
|
+
|
|
22
|
+
- retry budgets survive process restarts
|
|
23
|
+
- a stable key prevents the same logical job from being retried forever
|
|
24
|
+
- exhausted jobs can be pruned later from the CLI
|
|
25
|
+
|
|
26
|
+
## Boundaries
|
|
27
|
+
|
|
28
|
+
- this is retry state, not a full message lifecycle
|
|
29
|
+
- each logical job needs a stable retry key
|
|
30
|
+
|
|
31
|
+
## Minimal flow
|
|
32
|
+
|
|
33
|
+
Run this once in an environment where `localqueue` is importable.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
python - <<'PY'
|
|
37
|
+
from localqueue.retry import PersistentRetryExhausted, persistent_retry
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@persistent_retry(
|
|
41
|
+
key="invoice:1001",
|
|
42
|
+
max_tries=3,
|
|
43
|
+
)
|
|
44
|
+
def flaky() -> None:
|
|
45
|
+
print("calling remote API")
|
|
46
|
+
raise ConnectionError("temporary upstream failure")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
flaky()
|
|
51
|
+
except PersistentRetryExhausted as exc:
|
|
52
|
+
print(f"exhausted: {exc.key} after {exc.attempts} attempts")
|
|
53
|
+
except Exception as exc:
|
|
54
|
+
print(f"failed this run: {exc}")
|
|
55
|
+
PY
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Run the same command again. The retry state is reused because the key is
|
|
59
|
+
stable and the attempt store is on disk. After the budget is exhausted,
|
|
60
|
+
`PersistentRetryExhausted` is raised before the wrapped function runs again.
|
|
61
|
+
|
|
62
|
+
Inspect or prune exhausted retry records later:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
localqueue retry prune \
|
|
66
|
+
--dry-run \
|
|
67
|
+
--older-than 0
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## When to choose something else
|
|
71
|
+
|
|
72
|
+
Use a queue instead when work must be stored for later delivery, acknowledged
|
|
73
|
+
after success, released for another attempt, or moved to dead-letter on final
|
|
74
|
+
failure.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
icon: lucide/briefcase
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Use Cases
|
|
6
|
+
|
|
7
|
+
`localqueue` shines when the work can stay on one machine and the value is in
|
|
8
|
+
durable retries, local recovery, and simple terminal operations.
|
|
9
|
+
|
|
10
|
+
The pages in this section focus on workflows that are already well supported by
|
|
11
|
+
the current queue, worker, dead-letter, and persistent-retry APIs.
|
|
12
|
+
|
|
13
|
+
By default, `localqueue` stores queue and retry state under the usual XDG data
|
|
14
|
+
location for your user.
|
|
15
|
+
|
|
16
|
+
## The three strongest workflows
|
|
17
|
+
|
|
18
|
+
### Local outbox
|
|
19
|
+
|
|
20
|
+
Accept work now, run the external side effect later on the same machine.
|
|
21
|
+
|
|
22
|
+
This is the best fit when the caller should return quickly and the worker can
|
|
23
|
+
consume the job from a shared local store.
|
|
24
|
+
|
|
25
|
+
[Read the local outbox guide](use-cases/local-outbox.md)
|
|
26
|
+
|
|
27
|
+
### Operator recovery
|
|
28
|
+
|
|
29
|
+
Inspect failed jobs, review dead-letter records, and replay them from the
|
|
30
|
+
terminal after the underlying issue is fixed.
|
|
31
|
+
|
|
32
|
+
This is the best fit when a human operator needs a local recovery loop instead
|
|
33
|
+
of ad hoc shell scripts.
|
|
34
|
+
|
|
35
|
+
[Read the operator recovery guide](use-cases/operator-recovery.md)
|
|
36
|
+
|
|
37
|
+
### Persistent retries
|
|
38
|
+
|
|
39
|
+
Keep retry budgets across restarts when another part of the system already
|
|
40
|
+
delivers the work.
|
|
41
|
+
|
|
42
|
+
This is the best fit when you need durable attempt tracking but not a queue
|
|
43
|
+
lifecycle.
|
|
44
|
+
|
|
45
|
+
[Read the persistent retries guide](use-cases/persistent-retries.md)
|
|
46
|
+
|
|
47
|
+
## When these use cases do not fit
|
|
48
|
+
|
|
49
|
+
Use a broker or managed queue instead when:
|
|
50
|
+
|
|
51
|
+
- producers and consumers run on different machines
|
|
52
|
+
- strict global ordering matters
|
|
53
|
+
- very high write concurrency is expected
|
|
54
|
+
- queue retention, fan-out, or cross-service coordination is a first-class need
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
icon: lucide/briefcase
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# Use Cases
|
|
6
|
-
|
|
7
|
-
These examples are meant to be copied and run from any directory.
|
|
8
|
-
They keep state under `/tmp/localqueue-use-cases` so they do not interfere with
|
|
9
|
-
your normal localqueue data.
|
|
10
|
-
|
|
11
|
-
## Command style
|
|
12
|
-
|
|
13
|
-
The examples below use the direct CLI form:
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
localqueue queue exec ...
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Install `localqueue[cli]` first if the `localqueue` command is not available.
|
|
20
|
-
If you prefer `pipx run` or `uvx`, translate the command locally after you
|
|
21
|
-
understand the workflow.
|
|
22
|
-
|
|
23
|
-
## Setup
|
|
24
|
-
|
|
25
|
-
Create a scratch directory and a small worker script once:
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
mkdir -p /tmp/localqueue-use-cases
|
|
29
|
-
cat > /tmp/localqueue-use-cases/email_worker.py <<'PY'
|
|
30
|
-
from __future__ import annotations
|
|
31
|
-
|
|
32
|
-
import json
|
|
33
|
-
import sys
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
payload = json.load(sys.stdin)
|
|
37
|
-
address = payload["to"]
|
|
38
|
-
if payload.get("fail"):
|
|
39
|
-
raise ConnectionError(f"could not deliver email to {address}")
|
|
40
|
-
print(f"sent email to {address}")
|
|
41
|
-
PY
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
## 1. Local email spooler
|
|
45
|
-
|
|
46
|
-
This is the simplest shape for `localqueue`: one process enqueues work and
|
|
47
|
-
another process handles it later on the same machine.
|
|
48
|
-
|
|
49
|
-
Enqueue one email job:
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
localqueue queue add emails \
|
|
53
|
-
--store-path /tmp/localqueue-use-cases/emails.sqlite3 \
|
|
54
|
-
--value '{"to":"user@example.com"}'
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
Inspect the queue before processing:
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
localqueue queue stats emails \
|
|
61
|
-
--store-path /tmp/localqueue-use-cases/emails.sqlite3
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Process one queued message with the worker script:
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
localqueue queue exec emails \
|
|
68
|
-
--store-path /tmp/localqueue-use-cases/emails.sqlite3 \
|
|
69
|
-
-- python /tmp/localqueue-use-cases/email_worker.py
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
Check that the queue is empty again:
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
localqueue queue stats emails \
|
|
76
|
-
--store-path /tmp/localqueue-use-cases/emails.sqlite3
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
Use this pattern when your app should accept work quickly and let a local
|
|
80
|
-
worker perform the external side effect later.
|
|
81
|
-
|
|
82
|
-
## 2. Dead-letter and recover a failed command
|
|
83
|
-
|
|
84
|
-
This recipe shows a practical operator loop: enqueue a job that fails, inspect
|
|
85
|
-
the dead-letter list, then requeue it after fixing the payload.
|
|
86
|
-
|
|
87
|
-
Create a failing job:
|
|
88
|
-
|
|
89
|
-
```bash
|
|
90
|
-
localqueue queue add emails \
|
|
91
|
-
--store-path /tmp/localqueue-use-cases/failing-emails.sqlite3 \
|
|
92
|
-
--value '{"to":"broken@example.com","fail":true}'
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
Process it with a small retry budget so it reaches dead-letter quickly:
|
|
96
|
-
|
|
97
|
-
```bash
|
|
98
|
-
localqueue queue exec emails \
|
|
99
|
-
--store-path /tmp/localqueue-use-cases/failing-emails.sqlite3 \
|
|
100
|
-
--retry-store-path /tmp/localqueue-use-cases/failing-emails-retries.sqlite3 \
|
|
101
|
-
--max-tries 2 \
|
|
102
|
-
-- python /tmp/localqueue-use-cases/email_worker.py
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
Inspect the dead-letter summary and full record:
|
|
106
|
-
|
|
107
|
-
```bash
|
|
108
|
-
localqueue queue dead emails \
|
|
109
|
-
--store-path /tmp/localqueue-use-cases/failing-emails.sqlite3 \
|
|
110
|
-
--summary
|
|
111
|
-
|
|
112
|
-
localqueue queue dead emails \
|
|
113
|
-
--store-path /tmp/localqueue-use-cases/failing-emails.sqlite3
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
If you want to replay everything in the dead-letter queue:
|
|
117
|
-
|
|
118
|
-
```bash
|
|
119
|
-
localqueue queue requeue-dead emails \
|
|
120
|
-
--store-path /tmp/localqueue-use-cases/failing-emails.sqlite3 \
|
|
121
|
-
--all
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
This is a good fit for webhook delivery, notification pipelines, and other
|
|
125
|
-
single-host jobs where operators need to inspect and replay failed work from
|
|
126
|
-
the terminal.
|
|
127
|
-
|
|
128
|
-
## 3. Persistent retries without a queue
|
|
129
|
-
|
|
130
|
-
Sometimes another system already delivers the job and all you need is retry
|
|
131
|
-
state that survives restarts. In that case, use `localqueue.retry` directly.
|
|
132
|
-
|
|
133
|
-
Run this once in an environment where `localqueue` is importable.
|
|
134
|
-
|
|
135
|
-
```bash
|
|
136
|
-
python - <<'PY'
|
|
137
|
-
from localqueue.retry import PersistentRetryExhausted, persistent_retry
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
@persistent_retry(
|
|
141
|
-
key="invoice:1001",
|
|
142
|
-
store_path="/tmp/localqueue-use-cases/retries.sqlite3",
|
|
143
|
-
max_tries=3,
|
|
144
|
-
)
|
|
145
|
-
def flaky() -> None:
|
|
146
|
-
print("calling remote API")
|
|
147
|
-
raise ConnectionError("temporary upstream failure")
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
try:
|
|
151
|
-
flaky()
|
|
152
|
-
except PersistentRetryExhausted as exc:
|
|
153
|
-
print(f"exhausted: {exc.key} after {exc.attempts} attempts")
|
|
154
|
-
except Exception as exc:
|
|
155
|
-
print(f"failed this run: {exc}")
|
|
156
|
-
PY
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
Run the same command again. The retry state is reused because the key is
|
|
160
|
-
stable and the attempt store is on disk. After the budget is exhausted,
|
|
161
|
-
`PersistentRetryExhausted` is raised before the wrapped function runs again.
|
|
162
|
-
|
|
163
|
-
You can later inspect or prune exhausted retry records:
|
|
164
|
-
|
|
165
|
-
```bash
|
|
166
|
-
localqueue retry prune \
|
|
167
|
-
--retry-store-path /tmp/localqueue-use-cases/retries.sqlite3 \
|
|
168
|
-
--dry-run \
|
|
169
|
-
--older-than 0
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
Use this shape when work already arrives from somewhere else, such as a cron
|
|
173
|
-
trigger, another queue, or an HTTP handler, and you only want durable retry
|
|
174
|
-
budgets.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|