localqueue 0.1.1__tar.gz → 0.3.0__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.
Files changed (41) hide show
  1. {localqueue-0.1.1 → localqueue-0.3.0}/CHANGELOG.md +24 -0
  2. localqueue-0.3.0/PKG-INFO +83 -0
  3. localqueue-0.3.0/README.md +48 -0
  4. {localqueue-0.1.1 → localqueue-0.3.0}/docs/api.md +36 -1
  5. localqueue-0.3.0/docs/compare.md +50 -0
  6. localqueue-0.3.0/docs/index.md +74 -0
  7. localqueue-0.3.0/docs/operational-maturity.md +137 -0
  8. {localqueue-0.1.1 → localqueue-0.3.0}/docs/queues.md +253 -6
  9. {localqueue-0.1.1 → localqueue-0.3.0}/docs/release.md +1 -1
  10. {localqueue-0.1.1 → localqueue-0.3.0}/docs/retries.md +95 -0
  11. localqueue-0.3.0/docs/stability.md +59 -0
  12. localqueue-0.3.0/examples/process_webhook.sh +10 -0
  13. localqueue-0.3.0/examples/sqlite_concurrency_benchmark.py +181 -0
  14. localqueue-0.3.0/examples/sqlite_process_harness.py +438 -0
  15. localqueue-0.3.0/localqueue/cli.py +1355 -0
  16. localqueue-0.3.0/localqueue/failure.py +27 -0
  17. {localqueue-0.1.1 → localqueue-0.3.0}/localqueue/queue.py +48 -0
  18. {localqueue-0.1.1 → localqueue-0.3.0}/localqueue/retry/store.py +80 -0
  19. {localqueue-0.1.1 → localqueue-0.3.0}/localqueue/store.py +717 -18
  20. localqueue-0.3.0/localqueue/worker.py +388 -0
  21. {localqueue-0.1.1 → localqueue-0.3.0}/pyproject.toml +1 -1
  22. localqueue-0.3.0/tests/test_cli.py +1977 -0
  23. localqueue-0.3.0/tests/test_process_harness.py +77 -0
  24. localqueue-0.3.0/tests/test_queue.py +1760 -0
  25. {localqueue-0.1.1 → localqueue-0.3.0}/tests/test_retry.py +65 -0
  26. {localqueue-0.1.1 → localqueue-0.3.0}/uv.lock +1 -1
  27. localqueue-0.1.1/PKG-INFO +0 -364
  28. localqueue-0.1.1/README.md +0 -329
  29. localqueue-0.1.1/docs/index.md +0 -208
  30. localqueue-0.1.1/docs/operational-maturity.md +0 -96
  31. localqueue-0.1.1/localqueue/cli.py +0 -744
  32. localqueue-0.1.1/localqueue/worker.py +0 -192
  33. localqueue-0.1.1/tests/test_cli.py +0 -988
  34. localqueue-0.1.1/tests/test_queue.py +0 -907
  35. {localqueue-0.1.1 → localqueue-0.3.0}/.gitignore +0 -0
  36. {localqueue-0.1.1 → localqueue-0.3.0}/LICENSE +0 -0
  37. {localqueue-0.1.1 → localqueue-0.3.0}/examples/email_worker.py +0 -0
  38. {localqueue-0.1.1 → localqueue-0.3.0}/examples/enqueue_email.py +0 -0
  39. {localqueue-0.1.1 → localqueue-0.3.0}/localqueue/__init__.py +0 -0
  40. {localqueue-0.1.1 → localqueue-0.3.0}/localqueue/retry/__init__.py +0 -0
  41. {localqueue-0.1.1 → localqueue-0.3.0}/localqueue/retry/tenacity.py +0 -0
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.3.0
6
+
7
+ - Add a concurrent SQLite stress test for multiple producers and consumers.
8
+ - Add `examples/sqlite_concurrency_benchmark.py` for measuring local queue throughput.
9
+ - Make the SQLite benchmark reset its store and retry transient lock contention.
10
+ - Add `examples/sqlite_process_harness.py` for process-level throughput and crash-recovery checks.
11
+ - Add process-based stress tests for SQLite producer and consumer coordination.
12
+ - Add dead-letter filters and summaries for `queue dead`.
13
+ - Add enqueue deduplication with `--dedupe-key` and `dedupe_key=`.
14
+ - Add queue-level retry defaults that workers can inherit from `PersistentQueue`.
15
+ - Add worker rate limiting and circuit-breaker controls.
16
+ - Add usage docs for rate limiting, circuit breaker, and queue positioning.
17
+ - Add a short-term maturity note focused on performance and guarantees.
18
+
19
+ ## 0.2.0
20
+
21
+ - Add `queue stats --watch` for monitoring queue counts while workers run.
22
+ - Add `queue dead --watch` for repeatedly listing dead letters.
23
+ - Add `queue requeue-dead --all` for bulk recovery after a fix.
24
+ - Add structured `command not found` handling for `queue exec`.
25
+ - Add `examples/process_webhook.sh` as a shell/curl worker example.
26
+
3
27
  ## 0.1.1
4
28
 
5
29
  - Add project URLs for PyPI metadata.
@@ -0,0 +1,83 @@
1
+ Metadata-Version: 2.4
2
+ Name: localqueue
3
+ Version: 0.3.0
4
+ Summary: Durable local queues for Python, with persistent retry state powered by Tenacity.
5
+ Project-URL: Homepage, https://brunoportis.github.io/localqueue/
6
+ Project-URL: Documentation, https://brunoportis.github.io/localqueue/
7
+ Project-URL: Repository, https://github.com/brunoportis/localqueue
8
+ Project-URL: Issues, https://github.com/brunoportis/localqueue/issues
9
+ Author: Bruno Portis
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.11
21
+ Requires-Dist: tenacity>=9.1.4
22
+ Provides-Extra: all
23
+ Requires-Dist: lmdb>=2.2.0; extra == 'all'
24
+ Requires-Dist: pyyaml>=6.0.3; extra == 'all'
25
+ Requires-Dist: rich>=15.0.0; extra == 'all'
26
+ Requires-Dist: typer>=0.16.0; extra == 'all'
27
+ Provides-Extra: cli
28
+ Requires-Dist: pyyaml>=6.0.3; extra == 'cli'
29
+ Requires-Dist: rich>=15.0.0; extra == 'cli'
30
+ Requires-Dist: typer>=0.16.0; extra == 'cli'
31
+ Provides-Extra: lmdb
32
+ Requires-Dist: lmdb>=2.2.0; extra == 'lmdb'
33
+ Provides-Extra: sqlite
34
+ Description-Content-Type: text/markdown
35
+
36
+ # `localqueue`
37
+
38
+ [![Tests](https://github.com/brunoportis/localqueue/actions/workflows/tests.yml/badge.svg)](https://github.com/brunoportis/localqueue/actions/workflows/tests.yml)
39
+ [![CodeQL](https://github.com/brunoportis/localqueue/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/brunoportis/localqueue/actions/workflows/github-code-scanning/codeql)
40
+ ![Coverage](https://img.shields.io/badge/coverage-%E2%89%A595%25-brightgreen)
41
+
42
+ `localqueue` is a small durable queue for one machine. It stores work on the local filesystem by default and keeps retry state with Tenacity.
43
+
44
+ Use it for scripts, CLI tools, cron jobs, and small Python workers that share one local store. Use `localqueue.retry` when another system already delivers work and you only need retry state that survives restarts.
45
+
46
+ ```bash
47
+ localqueue queue exec emails -- python scripts/send_email.py
48
+ ```
49
+
50
+ ```python
51
+ from localqueue import PersistentQueue, persistent_worker
52
+
53
+ queue = PersistentQueue("emails")
54
+ queue.put({"to": "user@example.com"})
55
+
56
+ @persistent_worker(queue)
57
+ def send_email(job: dict[str, str]) -> None:
58
+ deliver(job["to"])
59
+ ```
60
+
61
+ Full docs live at [brunoportis.github.io/localqueue](https://brunoportis.github.io/localqueue/). The source docs are in [`docs/`](docs/).
62
+
63
+ ## Install
64
+
65
+ ```bash
66
+ pip install localqueue
67
+ ```
68
+
69
+ For the CLI:
70
+
71
+ ```bash
72
+ pip install "localqueue[cli]"
73
+ ```
74
+
75
+ For the optional LMDB backend:
76
+
77
+ ```bash
78
+ pip install "localqueue[lmdb]"
79
+ ```
80
+
81
+ ## When not to use
82
+
83
+ `localqueue` is not a distributed broker. If you need multi-host coordination, high write concurrency, managed retention, or strict cross-service ordering, use a system designed for that operating model.
@@ -0,0 +1,48 @@
1
+ # `localqueue`
2
+
3
+ [![Tests](https://github.com/brunoportis/localqueue/actions/workflows/tests.yml/badge.svg)](https://github.com/brunoportis/localqueue/actions/workflows/tests.yml)
4
+ [![CodeQL](https://github.com/brunoportis/localqueue/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/brunoportis/localqueue/actions/workflows/github-code-scanning/codeql)
5
+ ![Coverage](https://img.shields.io/badge/coverage-%E2%89%A595%25-brightgreen)
6
+
7
+ `localqueue` is a small durable queue for one machine. It stores work on the local filesystem by default and keeps retry state with Tenacity.
8
+
9
+ Use it for scripts, CLI tools, cron jobs, and small Python workers that share one local store. Use `localqueue.retry` when another system already delivers work and you only need retry state that survives restarts.
10
+
11
+ ```bash
12
+ localqueue queue exec emails -- python scripts/send_email.py
13
+ ```
14
+
15
+ ```python
16
+ from localqueue import PersistentQueue, persistent_worker
17
+
18
+ queue = PersistentQueue("emails")
19
+ queue.put({"to": "user@example.com"})
20
+
21
+ @persistent_worker(queue)
22
+ def send_email(job: dict[str, str]) -> None:
23
+ deliver(job["to"])
24
+ ```
25
+
26
+ Full docs live at [brunoportis.github.io/localqueue](https://brunoportis.github.io/localqueue/). The source docs are in [`docs/`](docs/).
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ pip install localqueue
32
+ ```
33
+
34
+ For the CLI:
35
+
36
+ ```bash
37
+ pip install "localqueue[cli]"
38
+ ```
39
+
40
+ For the optional LMDB backend:
41
+
42
+ ```bash
43
+ pip install "localqueue[lmdb]"
44
+ ```
45
+
46
+ ## When not to use
47
+
48
+ `localqueue` is not a distributed broker. If you need multi-host coordination, high write concurrency, managed retention, or strict cross-service ordering, use a system designed for that operating model.
@@ -24,6 +24,7 @@ Constructor options:
24
24
  | `store_path` | path for the default SQLite queue store |
25
25
  | `lease_timeout` | seconds before an inflight message is redelivered |
26
26
  | `maxsize` | maximum number of ready messages; `0` means unbounded |
27
+ | `retry_defaults` | Tenacity retry keyword defaults inherited by workers |
27
28
 
28
29
  Core methods:
29
30
 
@@ -44,6 +45,8 @@ Core methods:
44
45
  | `stats()` | count ready, delayed, inflight, dead, and total messages |
45
46
  | `dead_letters(limit=None)` | list dead-letter messages |
46
47
  | `requeue_dead(message, delay=0.0)` | return a dead-letter message to ready delivery |
48
+ | `prune_dead_letters(older_than)` | remove dead letters older than the given age |
49
+ | `count_dead_letters_older_than(older_than)` | count dead letters older than the given age without removing them |
47
50
  | `empty()` | whether there are no ready messages |
48
51
  | `full()` | whether ready capacity is reached |
49
52
  | `purge()` | remove all queue records |
@@ -74,8 +77,21 @@ Constructor options:
74
77
  | `dead_letter_on_failure` | dead-letter final handler failures when `True` |
75
78
  | `dead_letter_on_exhaustion` | compatibility alias for `dead_letter_on_failure` |
76
79
  | `release_delay` | delay used when releasing failed messages |
80
+ | `min_interval` | minimum seconds to wait between worker message starts |
81
+ | `circuit_breaker_failures` | consecutive recoverable failures before pausing the worker |
82
+ | `circuit_breaker_cooldown` | pause duration after the breaker opens |
77
83
  | `**retry_kwargs` | forwarded to `PersistentRetrying` |
78
84
 
85
+ Queue-level retry defaults can also be attached to `PersistentQueue` and are
86
+ merged into worker retry kwargs before explicit worker overrides. That keeps
87
+ shared queue policies close to the queue definition while still letting a
88
+ worker override a specific retry parameter when needed.
89
+
90
+ `min_interval` is a per-worker rate limit. Set it when a handler talks to an
91
+ external service that should not be hit back-to-back. The circuit-breaker pair
92
+ (`circuit_breaker_failures` + `circuit_breaker_cooldown`) opens after repeated
93
+ recoverable failures and pauses the worker before it fetches the next message.
94
+
79
95
  #### `QueueMessage`
80
96
 
81
97
  Dataclass returned by `put()` and `get_message()`.
@@ -91,12 +107,17 @@ Dataclass returned by `put()` and `get_message()`.
91
107
  | `available_at` | earliest delivery timestamp |
92
108
  | `leased_until` | lease expiration timestamp, if inflight |
93
109
  | `leased_by` | optional worker id that currently owns the lease, if inflight |
110
+ | `dedupe_key` | optional idempotency key used to reuse the same stored message |
111
+ | `attempt_history` | list of lease and outcome events recorded for this message |
94
112
  | `last_error` | structured error from the most recent failed processing attempt, if recorded |
95
113
  | `failed_at` | timestamp for `last_error`, if recorded |
96
114
 
97
115
  For `localqueue queue exec` failures, `last_error` also includes `command`,
98
116
  `exit_code`, `stdout`, and `stderr` fields so command workers can be inspected
99
- from `queue inspect` and `queue dead`.
117
+ from `queue inspect` and `queue dead`. `dedupe_key` is returned on inspection
118
+ and lets repeated enqueues reuse the same message until it is acknowledged or
119
+ cleaned up. `attempt_history` shows the lease and terminal events that led to
120
+ the current state.
100
121
 
101
122
  #### `QueueStats`
102
123
 
@@ -109,6 +130,12 @@ Dataclass returned by `stats()`.
109
130
  | `inflight` | leased messages not yet acknowledged, released, or dead-lettered |
110
131
  | `dead` | dead-letter messages hidden from normal delivery |
111
132
  | `total` | all messages still stored for the queue |
133
+ | `by_worker_id` | current inflight counts grouped by `leased_by` |
134
+ | `leases_by_worker_id` | historical lease counts grouped by `leased_by`, a coarse throughput proxy |
135
+ | `last_seen_by_worker_id` | most recent heartbeat timestamp for each recorded worker id |
136
+ | `oldest_ready_age_seconds` | age of the oldest ready message currently waiting |
137
+ | `oldest_inflight_age_seconds` | age of the oldest current inflight lease |
138
+ | `average_inflight_age_seconds` | average age across current inflight leases |
112
139
 
113
140
  ### Worker decorators
114
141
 
@@ -155,6 +182,10 @@ Protocol for custom queue stores.
155
182
  SQLite-backed queue store. This is the default backend. Records are serialized
156
183
  as versioned JSON; values must be JSON-serializable.
157
184
 
185
+ The SQLite store tracks its on-disk schema version with `PRAGMA user_version`.
186
+ Current releases accept older compatible versions and reject future versions
187
+ until a migration is defined.
188
+
158
189
  #### `LMDBQueueStore`
159
190
 
160
191
  LMDB-backed queue store. Records are serialized as versioned JSON; values must be
@@ -168,6 +199,9 @@ Thread-safe in-memory queue store for tests.
168
199
 
169
200
  Raised when LMDB reports that the queue store is locked by another process.
170
201
 
202
+ Install `localqueue[cli]` when you want the CLI entry points, and
203
+ `localqueue[lmdb]` when you want the LMDB queue store backend.
204
+
171
205
  ## localqueue.retry
172
206
 
173
207
  ### Retry decorators
@@ -316,6 +350,7 @@ class AttemptStore:
316
350
  def load(self, key: str) -> RetryRecord | None: ...
317
351
  def save(self, key: str, record: RetryRecord) -> None: ...
318
352
  def delete(self, key: str) -> None: ...
353
+ def prune_exhausted(self, *, older_than: float, now: float) -> int: ...
319
354
  ```
320
355
 
321
356
  #### `LMDBAttemptStore`
@@ -0,0 +1,50 @@
1
+ ---
2
+ icon: lucide/inbox
3
+ ---
4
+
5
+ # Compare
6
+
7
+ `localqueue` is for local, durable queues on one machine. If your jobs need a broker, a cluster, or managed retention, pick a tool that is built for that shape.
8
+
9
+ ## Use `localqueue` when
10
+
11
+ - you want a local filesystem-backed queue
12
+ - jobs are handled by scripts, CLIs, or small Python workers
13
+ - you care more about operational simplicity than distributed scheduling
14
+ - at-least-once delivery is acceptable
15
+ - you want to inspect and requeue failed work from the terminal
16
+ - you want worker-side controls like `min_interval`, queue-level retry defaults,
17
+ and a simple circuit breaker without introducing a broker
18
+
19
+ ## Reach for something else when
20
+
21
+ - producers and consumers must run on different machines
22
+ - you need broker-managed delivery guarantees
23
+ - write contention is high
24
+ - retention, metrics, sharding, or fanout are first-class requirements
25
+ - you want a queue that is already part of a larger distributed platform
26
+ - you need broker-level rate limiting or cross-host worker coordination
27
+
28
+ ## Quick comparison
29
+
30
+ | System | Best fit | Why choose it instead |
31
+ | --- | --- | --- |
32
+ | `localqueue` | single-host workers, scripts, local ops | simplest path when the queue can live on one filesystem |
33
+ | Celery | Python task orchestration with a broker | richer distributed worker ecosystem |
34
+ | RQ | Redis-backed Python jobs | simpler Redis-based queueing for server-side apps |
35
+ | Dramatiq | Python workers with broker support | clean task API for distributed workers |
36
+ | SQS | managed cloud queueing | external durability and multi-host coordination |
37
+ | RabbitMQ | brokered messaging | mature routing and delivery primitives |
38
+ | Redis Streams | stream processing and consumer groups | shared stream coordination with Redis |
39
+ | Kafka | high-throughput event pipelines | log-based distributed ingestion and replay |
40
+
41
+ ## Rule of thumb
42
+
43
+ If the queue can stay local and the value is in simple recovery, `localqueue` fits.
44
+ If the queue itself is part of a distributed system boundary, use a broker or stream platform.
45
+
46
+ See also:
47
+
48
+ - [Operational maturity](operational-maturity.md)
49
+ - [Persistent queues](queues.md)
50
+ - [Persistent retries](retries.md)
@@ -0,0 +1,74 @@
1
+ ---
2
+ icon: lucide/inbox
3
+ ---
4
+
5
+ # Overview
6
+
7
+ `localqueue` is a small durable queue for one machine. It stores work on the local filesystem by default and keeps retry state with Tenacity.
8
+
9
+ It fits scripts, CLI tools, cron jobs, development helpers, and small workers that can safely share one local store. It is not a distributed broker and does not provide multi-host coordination.
10
+
11
+ !!! note
12
+ The default model is local-file storage, at-least-once delivery, and best-effort ordering under concurrency.
13
+
14
+ ## CLI
15
+
16
+ Use the CLI when you want to enqueue, inspect, watch, and recover jobs from the terminal.
17
+
18
+ ```bash
19
+ localqueue queue add emails --value '{"to":"user@example.com"}'
20
+ localqueue queue exec emails -- python scripts/send_email.py
21
+ localqueue queue stats emails --watch --interval 1
22
+ localqueue queue dead emails --summary
23
+ localqueue queue health emails
24
+ ```
25
+
26
+ `queue exec` runs an external command per message. The message value is written to stdin as JSON, and exit code `0` acks the message.
27
+
28
+ For more command examples, see [Persistent queues](queues.md).
29
+
30
+ ## Library
31
+
32
+ Use the Python API when the worker lives in your codebase.
33
+
34
+ ```python
35
+ from localqueue import PersistentQueue, persistent_worker
36
+
37
+ queue = PersistentQueue("emails")
38
+ queue.put({"to": "user@example.com"})
39
+
40
+ @persistent_worker(queue)
41
+ def send_email(job: dict[str, str]) -> None:
42
+ deliver(job["to"])
43
+ ```
44
+
45
+ `localqueue.retry` stays available when another system already delivers work and you only need retry state.
46
+
47
+ See [Persistent retries](retries.md) for the retry API and store options.
48
+
49
+ ## When to use
50
+
51
+ - small Python workers on one machine
52
+ - scripts that need durable work after restarts
53
+ - terminal-driven queues for jobs you want to inspect and requeue
54
+ - retry state that must survive process restarts
55
+
56
+ ## When not to use
57
+
58
+ - multi-host scheduling
59
+ - high write concurrency
60
+ - managed retention or broker-level metrics
61
+ - strict global ordering
62
+ - distributed coordination
63
+
64
+ See [Compare](compare.md) for a short decision guide.
65
+
66
+ ## Read more
67
+
68
+ - [Persistent queues](queues.md)
69
+ - [Persistent retries](retries.md)
70
+ - [Stability](stability.md)
71
+ - [Compare](compare.md)
72
+ - [API reference](api.md)
73
+ - [Operational maturity](operational-maturity.md)
74
+ - [Release checklist](release.md)
@@ -0,0 +1,137 @@
1
+ ---
2
+ icon: lucide/list-checks
3
+ ---
4
+
5
+ # Operational Maturity Checklist
6
+
7
+ This project is intentionally small: durable local queues for Python workers,
8
+ plus persistent retry state powered by Tenacity. The checklist below tracks the
9
+ work needed before describing it as a mature production queue system instead of
10
+ a local worker library.
11
+
12
+ ## Near-term focus
13
+
14
+ The next release should improve two things that matter for real local workers:
15
+
16
+ - performance, especially under multiple producers and consumers sharing one
17
+ SQLite file
18
+ - guarantees, especially where the project is intentionally at-least-once and
19
+ best-effort rather than distributed or strongly ordered
20
+
21
+ That means measuring the current ceiling before promising more:
22
+
23
+ - benchmark WAL contention and queue throughput with concurrent processes
24
+ - use `examples/sqlite_concurrency_benchmark.py` to get a repeatable local
25
+ throughput snapshot
26
+ - a quick local run on the development machine stayed in the low thousands of
27
+ messages per second and handled a few producers and consumers without failing
28
+ the queue, which is useful as a local ceiling check but not a production
29
+ guarantee
30
+ - exercise abrupt consumer exit and lease recovery with
31
+ `examples/sqlite_process_harness.py --mode crash-recovery`
32
+ - document the practical limits for a single host and a single SQLite file
33
+ - keep ordering language explicit: best effort under concurrency, not strict
34
+ global ordering
35
+ - keep delivery language explicit: at least once, not exactly once
36
+ - keep recovery language explicit: inspection and requeue are local operations,
37
+ not cross-cluster coordination
38
+
39
+ ## Concurrency and locking
40
+
41
+ - [x] Document practical SQLite concurrency limits for producers and workers.
42
+ - [x] Add stress tests for multiple producer and consumer processes sharing one
43
+ SQLite queue file.
44
+ - [x] Measure throughput and lock contention under WAL mode.
45
+ - [ ] Define guidance for when users should move to Postgres, Redis, SQS,
46
+ RabbitMQ, or another external broker.
47
+
48
+ ## Distributed operation
49
+
50
+ - [x] Decide whether multi-host operation is in scope.
51
+ - [x] Add explicit documentation that the default model is local-file storage,
52
+ not distributed coordination.
53
+ - [x] Evaluate worker heartbeats beyond lease expiration.
54
+ - [x] Evaluate worker identity, liveness inspection, and stale-worker reporting.
55
+ - [x] Evaluate sharding or namespacing guidance for independent queues.
56
+
57
+ `localqueue` does not try to coordinate work across multiple machines. The
58
+ supported model is a single host, one local store, and local recovery through
59
+ inspection, retry, and requeue. Heartbeats and stale-worker reporting are
60
+ inspection tools for that local model, not a distributed liveness protocol.
61
+
62
+ ## Delivery semantics
63
+
64
+ - [x] Document at-least-once delivery prominently in the README and queue docs.
65
+ - [x] Add idempotency guidance for handlers.
66
+ - [x] Provide examples for idempotent job keys and external side effects.
67
+ - [x] Evaluate deduplication support for enqueue operations.
68
+ - [x] Evaluate stronger ordering guarantees, or document why ordering is best
69
+ effort under concurrency.
70
+ - [x] Document the failure window between a successful handler side effect and
71
+ `ack()`.
72
+
73
+ ## Retry and worker policies
74
+
75
+ - [x] Add examples for different policies by exception type.
76
+ - [x] Evaluate built-in permanent-failure classification.
77
+ - [x] Evaluate queue-level retry defaults that can be reused by many workers.
78
+ - [x] Evaluate rate limiting for handlers that call external services.
79
+ - [x] Evaluate circuit-breaker integration or documentation.
80
+ - [x] Add clearer guidance for `release_delay`, Tenacity `wait`, and
81
+ `lease_timeout` interactions.
82
+ - [x] Reject ambiguous worker-loop combinations such as `--forever` with
83
+ `--max-jobs`.
84
+
85
+ ## Observability
86
+
87
+ - [ ] Add optional Prometheus or OpenTelemetry metrics.
88
+ - [x] Track per-message attempt history, not only the latest error.
89
+ - [x] Track queue latency, handler duration, and time spent inflight.
90
+ - [x] Track throughput by queue and worker id.
91
+ - [x] Add aggregate failure summaries for dead-letter inspection.
92
+ - [x] Evaluate structured event logging for enqueue, lease, ack, release, and
93
+ dead-letter transitions.
94
+
95
+ ## Storage operations
96
+
97
+ - [x] Define a schema migration strategy for SQLite stores.
98
+ - [x] Document backup and restore expectations.
99
+ - [x] Add maintenance guidance for `VACUUM`, WAL files, and long-lived stores.
100
+ - [x] Add retention or cleanup controls for dead-letter records.
101
+ - [x] Add cleanup controls for old retry records.
102
+ - [x] Add preview mode before cleanup removes records.
103
+ - [x] Add configurable retention defaults for dead letters and retry records.
104
+ - [x] Add a one-shot queue health summary for day-to-day checks.
105
+ - [x] Document recovery behavior for corrupted or partially written store files.
106
+ - [x] Add compatibility tests for SQLite store files created by older package
107
+ versions.
108
+
109
+ The SQLite store keeps a schema version in `PRAGMA user_version`. Current
110
+ releases accept older compatible versions and reject future versions they do
111
+ not know how to migrate yet. That keeps upgrades explicit and leaves a clear
112
+ path for future schema changes.
113
+
114
+ ## API stability
115
+
116
+ - [x] Decide which APIs are stable before a `1.0` release.
117
+ - [x] Document compatibility aliases and their planned lifetime.
118
+ - [x] Add deprecation policy.
119
+ - [ ] Increase coverage margin above the current 95% gate.
120
+ - [x] Keep docs aligned with defaults for SQLite, optional LMDB, and CLI extras.
121
+
122
+ ## Guardrails against misuse
123
+
124
+ - [x] Document recommended payload size limits.
125
+ - [x] Add clearer lease-timeout guidance for slow jobs.
126
+ - [x] Add examples for safe shutdown and long-running workers.
127
+ - [x] Add health-check examples for worker processes.
128
+ - [x] Add configuration validation for risky combinations where possible.
129
+ - [x] Add warnings or docs for unbounded queue growth.
130
+
131
+ ## Positioning
132
+
133
+ - [x] Keep public wording focused on durable local queues for scripts and small
134
+ workers.
135
+ - [x] Avoid presenting the project as a replacement for distributed brokers.
136
+ - [x] Add production-readiness notes that distinguish supported use cases from
137
+ future work.