localqueue 0.3.4__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.
Files changed (56) hide show
  1. {localqueue-0.3.4 → localqueue-0.4.1}/CHANGELOG.md +34 -0
  2. {localqueue-0.3.4 → localqueue-0.4.1}/PKG-INFO +16 -3
  3. {localqueue-0.3.4 → localqueue-0.4.1}/README.md +15 -2
  4. localqueue-0.4.1/docs/develop.md +137 -0
  5. {localqueue-0.3.4 → localqueue-0.4.1}/docs/index.md +17 -5
  6. {localqueue-0.3.4 → localqueue-0.4.1}/docs/queues.md +19 -4
  7. localqueue-0.4.1/docs/release.md +47 -0
  8. {localqueue-0.3.4 → localqueue-0.4.1}/docs/retries.md +21 -8
  9. localqueue-0.4.1/docs/use-cases/local-outbox.md +83 -0
  10. localqueue-0.4.1/docs/use-cases/operator-recovery.md +85 -0
  11. localqueue-0.4.1/docs/use-cases/persistent-retries.md +74 -0
  12. localqueue-0.4.1/docs/use-cases.md +54 -0
  13. {localqueue-0.3.4 → localqueue-0.4.1}/localqueue/cli.py +10 -0
  14. {localqueue-0.3.4 → localqueue-0.4.1}/pyproject.toml +1 -1
  15. {localqueue-0.3.4 → localqueue-0.4.1}/tests/test_cli.py +7 -0
  16. {localqueue-0.3.4 → localqueue-0.4.1}/uv.lock +1 -1
  17. localqueue-0.3.4/docs/release.md +0 -111
  18. {localqueue-0.3.4 → localqueue-0.4.1}/.gitignore +0 -0
  19. {localqueue-0.3.4 → localqueue-0.4.1}/LICENSE +0 -0
  20. {localqueue-0.3.4 → localqueue-0.4.1}/docs/api.md +0 -0
  21. {localqueue-0.3.4 → localqueue-0.4.1}/docs/compare.md +0 -0
  22. {localqueue-0.3.4 → localqueue-0.4.1}/docs/operational-maturity.md +0 -0
  23. {localqueue-0.3.4 → localqueue-0.4.1}/docs/stability.md +0 -0
  24. {localqueue-0.3.4 → localqueue-0.4.1}/examples/email_worker.py +0 -0
  25. {localqueue-0.3.4 → localqueue-0.4.1}/examples/enqueue_email.py +0 -0
  26. {localqueue-0.3.4 → localqueue-0.4.1}/examples/process_webhook.sh +0 -0
  27. {localqueue-0.3.4 → localqueue-0.4.1}/examples/retry_demo.py +0 -0
  28. {localqueue-0.3.4 → localqueue-0.4.1}/examples/sqlite_concurrency_benchmark.py +0 -0
  29. {localqueue-0.3.4 → localqueue-0.4.1}/examples/sqlite_process_harness.py +0 -0
  30. {localqueue-0.3.4 → localqueue-0.4.1}/localqueue/__init__.py +0 -0
  31. {localqueue-0.3.4 → localqueue-0.4.1}/localqueue/cli_commands.py +0 -0
  32. {localqueue-0.3.4 → localqueue-0.4.1}/localqueue/cli_support.py +0 -0
  33. {localqueue-0.3.4 → localqueue-0.4.1}/localqueue/cli_worker_commands.py +0 -0
  34. {localqueue-0.3.4 → localqueue-0.4.1}/localqueue/failure.py +0 -0
  35. {localqueue-0.3.4 → localqueue-0.4.1}/localqueue/paths.py +0 -0
  36. {localqueue-0.3.4 → localqueue-0.4.1}/localqueue/queue.py +0 -0
  37. {localqueue-0.3.4 → localqueue-0.4.1}/localqueue/retry/__init__.py +0 -0
  38. {localqueue-0.3.4 → localqueue-0.4.1}/localqueue/retry/stores/__init__.py +0 -0
  39. {localqueue-0.3.4 → localqueue-0.4.1}/localqueue/retry/stores/_shared.py +0 -0
  40. {localqueue-0.3.4 → localqueue-0.4.1}/localqueue/retry/stores/base.py +0 -0
  41. {localqueue-0.3.4 → localqueue-0.4.1}/localqueue/retry/stores/lmdb.py +0 -0
  42. {localqueue-0.3.4 → localqueue-0.4.1}/localqueue/retry/stores/memory.py +0 -0
  43. {localqueue-0.3.4 → localqueue-0.4.1}/localqueue/retry/stores/sqlite.py +0 -0
  44. {localqueue-0.3.4 → localqueue-0.4.1}/localqueue/retry/tenacity.py +0 -0
  45. {localqueue-0.3.4 → localqueue-0.4.1}/localqueue/services/__init__.py +0 -0
  46. {localqueue-0.3.4 → localqueue-0.4.1}/localqueue/services/queue_worker.py +0 -0
  47. {localqueue-0.3.4 → localqueue-0.4.1}/localqueue/stores/__init__.py +0 -0
  48. {localqueue-0.3.4 → localqueue-0.4.1}/localqueue/stores/_shared.py +0 -0
  49. {localqueue-0.3.4 → localqueue-0.4.1}/localqueue/stores/base.py +0 -0
  50. {localqueue-0.3.4 → localqueue-0.4.1}/localqueue/stores/lmdb.py +0 -0
  51. {localqueue-0.3.4 → localqueue-0.4.1}/localqueue/stores/memory.py +0 -0
  52. {localqueue-0.3.4 → localqueue-0.4.1}/localqueue/stores/sqlite.py +0 -0
  53. {localqueue-0.3.4 → localqueue-0.4.1}/localqueue/worker.py +0 -0
  54. {localqueue-0.3.4 → localqueue-0.4.1}/tests/test_process_harness.py +0 -0
  55. {localqueue-0.3.4 → localqueue-0.4.1}/tests/test_queue.py +0 -0
  56. {localqueue-0.3.4 → localqueue-0.4.1}/tests/test_retry.py +0 -0
@@ -1,5 +1,39 @@
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
+
13
+ ## [0.4.0](https://github.com/brunoportis/localqueue/compare/v0.3.4...v0.4.0) (2026-04-24)
14
+
15
+
16
+ ### Features
17
+
18
+ * add release-please automation and CLI --version option ([f8db976](https://github.com/brunoportis/localqueue/commit/f8db9767f45fc1eb5055ef0e7eb946aae3af3129))
19
+
20
+
21
+ ### Bug Fixes
22
+
23
+ * **release:** target uv.lock version explicitly ([3e71f44](https://github.com/brunoportis/localqueue/commit/3e71f4450e50de69488b18c04b018a6af00bf962))
24
+ * **release:** target uv.lock version explicitly ([31b45ce](https://github.com/brunoportis/localqueue/commit/31b45ce2ba3deea070d8d49c8bebebadcbc9fb37))
25
+ * **release:** track uv.lock in release-please ([897b86a](https://github.com/brunoportis/localqueue/commit/897b86aa33d9b38cba82da7b0e2623473488b94d))
26
+ * **release:** track uv.lock in release-please ([637ef7f](https://github.com/brunoportis/localqueue/commit/637ef7fca945f1153b643f27b0d6df6ccc39f778))
27
+ * **workflow:** configure git identity for release sync ([9872e7a](https://github.com/brunoportis/localqueue/commit/9872e7a85d1382a968f344444009e721da926e50))
28
+ * **workflow:** configure git identity for release sync ([2ef15fd](https://github.com/brunoportis/localqueue/commit/2ef15fdd3b5a877569b46980ef795442c1f3089c))
29
+ * **workflow:** sync uv.lock in release-please ([f26b1fa](https://github.com/brunoportis/localqueue/commit/f26b1fac2603f5ae0016ce994f5065291b6a6ad0))
30
+ * **workflow:** sync uv.lock in release-please ([193b4c6](https://github.com/brunoportis/localqueue/commit/193b4c6d5dba208ec43d7ea6ada402c5c4f1c2a5))
31
+
32
+
33
+ ### Documentation
34
+
35
+ * **use-cases:** simplify CLI examples ([84b92f2](https://github.com/brunoportis/localqueue/commit/84b92f2a420ddeb46dc8db2e6c1493fc45430bff))
36
+
3
37
  ## 0.3.4
4
38
 
5
39
  - Keep `queue exec` and `queue process` alive in `--forever --block` mode when the queue starts empty.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: localqueue
3
- Version: 0.3.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
  [![PyPI version](https://img.shields.io/pypi/v/localqueue.svg)](https://pypi.org/project/localqueue/)
41
41
  ![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen)
42
42
 
43
- `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
+ `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 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.
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,11 @@ 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
+
75
+ For development and release workflow details, see [`docs/develop.md`](docs/develop.md).
76
+
64
77
  ## Install
65
78
 
66
79
  ```bash
@@ -5,9 +5,17 @@
5
5
  [![PyPI version](https://img.shields.io/pypi/v/localqueue.svg)](https://pypi.org/project/localqueue/)
6
6
  ![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen)
7
7
 
8
- `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
+ `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 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.
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,11 @@ 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
+
40
+ For development and release workflow details, see [`docs/develop.md`](docs/develop.md).
41
+
29
42
  ## Install
30
43
 
31
44
  ```bash
@@ -0,0 +1,137 @@
1
+ ---
2
+ icon: lucide/hammer
3
+ ---
4
+
5
+ # Development Guide
6
+
7
+ This page describes the day-to-day development flow for `localqueue`: how to
8
+ write commits, how versions are released, what quality gates should pass before
9
+ merging, and what happens to docs and deployments.
10
+
11
+ ## Commit style
12
+
13
+ Use Conventional Commits.
14
+
15
+ Preferred shapes:
16
+
17
+ ```text
18
+ feat: add queue health summary
19
+ fix: keep forever workers alive on empty queues
20
+ docs: describe release flow
21
+ chore: refresh release-please config
22
+ test: cover version output in the CLI
23
+ ```
24
+
25
+ Keep commit messages short and specific. Use a scope only when it helps the
26
+ history, for example `fix(cli): ...` or `docs(release): ...`.
27
+
28
+ Why this matters here:
29
+
30
+ - `release-please` uses commit history to decide the next version.
31
+ - clean commit messages produce cleaner changelogs.
32
+ - release PRs become easier to review when each change has a clear intent.
33
+
34
+ ## Versioning
35
+
36
+ Versions follow SemVer-style `MAJOR.MINOR.PATCH`.
37
+
38
+ Before `1.0.0`, use this rule:
39
+
40
+ | Change | Version bump |
41
+ | --- | --- |
42
+ | Bug fix only | patch |
43
+ | New CLI/API feature | minor |
44
+ | Breaking user-facing behavior | minor, with a clear changelog note |
45
+
46
+ In this repo:
47
+
48
+ - `release-please` opens or updates the release PR.
49
+ - merging that release PR creates the tag and the GitHub Release.
50
+ - the release tag stays in the form `v<version>`.
51
+ - `pyproject.toml` is updated by the release automation, not by hand.
52
+
53
+ ## Quality gates before merge
54
+
55
+ Run the project checks before opening or merging a release-worthy change.
56
+
57
+ ```bash
58
+ env UV_CACHE_DIR=/tmp/uv-cache uv run ruff format .
59
+ env UV_CACHE_DIR=/tmp/uv-cache uv run ruff format --check .
60
+ env UV_CACHE_DIR=/tmp/uv-cache uv run pytest -q
61
+ env UV_CACHE_DIR=/tmp/uv-cache uv run ruff check .
62
+ env UV_CACHE_DIR=/tmp/uv-cache uv run basedpyright
63
+ env UV_CACHE_DIR=/tmp/uv-cache uv run zensical build --clean
64
+ env UV_CACHE_DIR=/tmp/uv-cache uv lock --check
65
+ env UV_CACHE_DIR=/tmp/uv-cache uv build
66
+ ```
67
+
68
+ Practical notes:
69
+
70
+ - `pytest` is the main behavior gate.
71
+ - `ruff format` applies the formatting fix locally.
72
+ - `ruff format --check` verifies formatting stays stable before a merge.
73
+ - `ruff check` and `basedpyright` keep the codebase clean and typed where it
74
+ matters.
75
+ - `zensical build --clean` catches docs breakage before it lands.
76
+ - `uv lock --check` confirms the lockfile is current.
77
+ - `uv build` verifies the package artifacts still build cleanly.
78
+
79
+ ## Documentation workflow
80
+
81
+ Docs should stay close to the code and the release flow.
82
+
83
+ - Update `docs/` when the CLI, public API, or release process changes.
84
+ - Keep `docs/release.md` focused on release automation and tagging.
85
+ - Keep `docs/operational-maturity.md` focused on runtime guarantees and
86
+ production boundaries.
87
+ - Update `docs/index.md` and the docs navigation when a new top-level doc is
88
+ added.
89
+
90
+ Good documentation changes usually include:
91
+
92
+ - a short user-facing description
93
+ - exact commands
94
+ - concrete examples
95
+ - the current expected release or deployment behavior
96
+
97
+ ## Deployments
98
+
99
+ The docs site is built from `docs/` with `zensical`.
100
+
101
+ The release workflow currently handles:
102
+
103
+ - PyPI publication
104
+ - GHCR image publication
105
+
106
+ The docs workflow currently handles:
107
+
108
+ - building the docs site on pushes and pull requests
109
+ - deploying the site from GitHub Actions on push to the configured branches
110
+
111
+ If a change affects packaging, release tags, or public docs, validate both the
112
+ package build and the docs build before merging.
113
+
114
+ ## Local release checklist
115
+
116
+ When preparing a release or release PR, verify:
117
+
118
+ 1. commits follow Conventional Commits
119
+ 2. tests pass
120
+ 3. lint and typecheck pass
121
+ 4. docs build
122
+ 5. package build succeeds
123
+ 6. the release notes and changelog reflect the change
124
+
125
+ ## Secrets and CI
126
+
127
+ The current release automation expects:
128
+
129
+ - `RELEASE_PLEASE_TOKEN` for `release-please`
130
+ - `SONAR_TOKEN` for SonarCloud, if the Sonar workflow is enabled
131
+
132
+ Keep secrets in repository settings, not in the repo.
133
+
134
+ ## Daily rule
135
+
136
+ If a change would confuse the next release PR, fix the commit message, docs, or
137
+ tests before merging it.
@@ -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 local filesystem by default and keeps retry state with Tenacity.
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 that can safely share one local store. It is not a distributed broker and does not provide multi-host coordination.
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
- localqueue queue add emails --value '{"to":"user@example.com"}'
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
 
@@ -65,10 +75,12 @@ See [Compare](compare.md) for a short decision guide.
65
75
 
66
76
  ## Read more
67
77
 
78
+ - [Use cases](use-cases.md)
68
79
  - [Persistent queues](queues.md)
69
80
  - [Persistent retries](retries.md)
70
81
  - [Stability](stability.md)
71
82
  - [Compare](compare.md)
72
83
  - [API reference](api.md)
73
84
  - [Operational maturity](operational-maturity.md)
85
+ - [Development guide](develop.md)
74
86
  - [Release checklist](release.md)
@@ -4,14 +4,28 @@ icon: lucide/inbox
4
4
 
5
5
  # Persistent Queues
6
6
 
7
- `localqueue` is the main workflow API in this project. It is a small queue
8
- abstraction backed by SQLite by default and designed for durable, at-least-once
9
- delivery of Python values.
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 cannot be empty and cannot contain `:`.
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.
@@ -0,0 +1,47 @@
1
+ ---
2
+ icon: lucide/package-check
3
+ ---
4
+
5
+ # Release
6
+
7
+ This project uses `release-please` to automate release PRs, changelog updates,
8
+ version bumps, Git tags, and GitHub Releases. The PyPI and GHCR publication
9
+ still happens in the existing release workflow after the release tag is
10
+ created.
11
+
12
+ ## Versioning
13
+
14
+ Use SemVer-style versions:
15
+
16
+ ```text
17
+ MAJOR.MINOR.PATCH
18
+ ```
19
+
20
+ Before `1.0.0`, use this convention:
21
+
22
+ | Change | Version bump |
23
+ | --- | --- |
24
+ | Bug fix only | patch, for example `0.1.1` |
25
+ | New CLI/API feature | minor, for example `0.2.0` |
26
+ | Breaking user-facing behavior | minor before `1.0.0`, with a clear changelog note |
27
+
28
+ ## How it works
29
+
30
+ 1. Merge Conventional Commit changes into `master`.
31
+ 2. `release-please` opens or updates a release PR with the changelog and the
32
+ version bump.
33
+ 3. Merge the release PR.
34
+ 4. `release-please` tags the merge commit and creates the GitHub Release.
35
+ 5. The existing `Release` workflow publishes the artifacts to PyPI and GHCR.
36
+
37
+ ## Setup notes
38
+
39
+ 1. The workflow expects a repository secret named `RELEASE_PLEASE_TOKEN`.
40
+ 2. The release tag format stays `v<version>` so the current publish workflow can
41
+ keep matching `v*` tags.
42
+ 3. Keep commit messages in Conventional Commit form so `release-please` can
43
+ classify changes correctly.
44
+
45
+ ## Current Release
46
+
47
+ The current planned release is `0.3.4`.
@@ -4,14 +4,27 @@ icon: lucide/rotate-cw
4
4
 
5
5
  # Persistent Retries
6
6
 
7
- `localqueue` is the durable retry layer used by queue workers and by code
8
- that already has its own delivery mechanism. It wraps Tenacity's `Retrying` and
9
- `AsyncRetrying` classes. You still configure `stop`, `wait`, `retry`, callbacks,
10
- and `retry_with()` the same way; the wrapper adds a durable attempt record before
11
- each stop decision.
12
-
13
- If you need a full job lifecycle with ack, release, leases, and dead-letter
14
- records, start with [Persistent queues](queues.md).
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
@@ -8,6 +8,7 @@ from contextlib import contextmanager
8
8
  from pathlib import Path
9
9
  from typing import Any, Iterator, TYPE_CHECKING
10
10
 
11
+ from . import __version__
11
12
  from .cli_commands import (
12
13
  CommandRegistryContext,
13
14
  register_config_commands,
@@ -75,6 +76,15 @@ def _build_app(typer: Any, yaml: Any, console: Any, err_console: Any) -> Any:
75
76
  queue_app = typer.Typer(no_args_is_help=True)
76
77
  retry_app = typer.Typer(no_args_is_help=True)
77
78
  config_app = typer.Typer(no_args_is_help=True)
79
+
80
+ @app.callback(invoke_without_command=True)
81
+ def root_callback(
82
+ version: bool = typer.Option(False, "--version", help="Show version and exit"),
83
+ ) -> None:
84
+ if version:
85
+ console.print(__version__)
86
+ raise typer.Exit()
87
+
78
88
  command_context = CommandRegistryContext(
79
89
  typer=typer,
80
90
  console=console,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "localqueue"
3
- version = "0.3.4"
3
+ version = "0.4.1"
4
4
  description = "Durable local queues for Python, with persistent retry state powered by Tenacity."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -215,6 +215,13 @@ class CliTests(unittest.TestCase):
215
215
  build_app.assert_called_once()
216
216
  app.assert_called_once_with(args=["--help"])
217
217
 
218
+ def test_root_version_option_prints_version(self) -> None:
219
+ with mock.patch("localqueue.cli.__version__", "9.9.9"):
220
+ result = self._invoke(["--version"])
221
+
222
+ self.assertEqual(result.exit_code, 0)
223
+ self.assertEqual(result.stdout.strip(), "9.9.9")
224
+
218
225
  def test_read_value_uses_argument_before_stdin(self) -> None:
219
226
  self.assertEqual(_read_value("{}"), "{}")
220
227
 
@@ -200,7 +200,7 @@ wheels = [
200
200
 
201
201
  [[package]]
202
202
  name = "localqueue"
203
- version = "0.3.4"
203
+ version = "0.4.1"
204
204
  source = { editable = "." }
205
205
  dependencies = [
206
206
  { name = "tenacity" },
@@ -1,111 +0,0 @@
1
- ---
2
- icon: lucide/package-check
3
- ---
4
-
5
- # Release
6
-
7
- This project currently uses a manual versioning flow. The release version lives
8
- in `pyproject.toml`, and each release should have a matching `CHANGELOG.md`
9
- entry and Git tag.
10
-
11
- ## Versioning
12
-
13
- Use SemVer-style versions:
14
-
15
- ```text
16
- MAJOR.MINOR.PATCH
17
- ```
18
-
19
- Before `1.0.0`, use this convention:
20
-
21
- | Change | Version bump |
22
- | --- | --- |
23
- | Bug fix only | patch, for example `0.1.1` |
24
- | New CLI/API feature | minor, for example `0.2.0` |
25
- | Breaking user-facing behavior | minor before `1.0.0`, with a clear changelog note |
26
-
27
- ## Checklist
28
-
29
- 1. Update `version` in `pyproject.toml`.
30
- 2. Run `uv lock` and commit the updated `uv.lock`.
31
- 3. Update `CHANGELOG.md`.
32
- 4. Confirm `requires-python`, license metadata, and PyPI classifiers still match
33
- supported Python versions and the intended release policy.
34
- 5. Run the test suite:
35
-
36
- ```bash
37
- uv run pytest
38
- ```
39
-
40
- 6. Run lint and typecheck:
41
-
42
- ```bash
43
- uv run ruff check .
44
- uv run basedpyright
45
- ```
46
-
47
- 7. Build the docs:
48
-
49
- ```bash
50
- uv run zensical build --clean
51
- ```
52
-
53
- 8. Confirm the lockfile is current:
54
-
55
- ```bash
56
- uv lock --check
57
- ```
58
-
59
- 9. Build the release artifacts:
60
-
61
- ```bash
62
- uv build
63
- ```
64
-
65
- 10. Inspect the artifacts:
66
-
67
- ```bash
68
- tar -tzf dist/localqueue-<version>.tar.gz | less
69
- python -m zipfile -l dist/localqueue-<version>-py3-none-any.whl
70
- ```
71
-
72
- 11. Install the wheel in a clean virtual environment:
73
-
74
- ```bash
75
- uv venv /tmp/localqueue-release-venv
76
- uv pip install --python /tmp/localqueue-release-venv/bin/python \
77
- "dist/localqueue-<version>-py3-none-any.whl[all]"
78
- /tmp/localqueue-release-venv/bin/localqueue --help
79
- ```
80
-
81
- 12. Run a local smoke test:
82
-
83
- ```bash
84
- uv run python examples/enqueue_email.py user@example.com \
85
- --store-path /tmp/localqueue-smoke
86
-
87
- uv run localqueue queue process emails examples.email_worker:send_email \
88
- --store-path /tmp/localqueue-smoke \
89
- --retry-store-path /tmp/localqueue-smoke-retries.sqlite3 \
90
- --worker-id worker-smoke \
91
- --max-tries 3
92
- ```
93
-
94
- 13. Create the release tag:
95
-
96
- ```bash
97
- git tag v<version>
98
- ```
99
-
100
- 14. Publish the artifacts.
101
-
102
- This release workflow also publishes the CLI image to GitHub Container
103
- Registry as `ghcr.io/brunoportis/localqueue:<tag>` and `:latest`.
104
-
105
- 15. Confirm the docs deployment finished successfully in the `Documentation`
106
- workflow. GitHub Pages must be configured to deploy from GitHub Actions in
107
- the repository settings.
108
-
109
- ## Current Release
110
-
111
- The current planned release is `0.3.4`.
File without changes
File without changes
File without changes
File without changes
File without changes