localqueue 0.3.4__tar.gz → 0.4.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.
- {localqueue-0.3.4 → localqueue-0.4.0}/CHANGELOG.md +24 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/PKG-INFO +3 -1
- {localqueue-0.3.4 → localqueue-0.4.0}/README.md +2 -0
- localqueue-0.4.0/docs/develop.md +137 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/docs/index.md +2 -0
- localqueue-0.4.0/docs/release.md +47 -0
- localqueue-0.4.0/docs/use-cases.md +174 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/localqueue/cli.py +10 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/pyproject.toml +1 -1
- {localqueue-0.3.4 → localqueue-0.4.0}/tests/test_cli.py +7 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/uv.lock +1 -1
- localqueue-0.3.4/docs/release.md +0 -111
- {localqueue-0.3.4 → localqueue-0.4.0}/.gitignore +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/LICENSE +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/docs/api.md +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/docs/compare.md +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/docs/operational-maturity.md +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/docs/queues.md +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/docs/retries.md +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/docs/stability.md +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/examples/email_worker.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/examples/enqueue_email.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/examples/process_webhook.sh +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/examples/retry_demo.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/examples/sqlite_concurrency_benchmark.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/examples/sqlite_process_harness.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/localqueue/__init__.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/localqueue/cli_commands.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/localqueue/cli_support.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/localqueue/cli_worker_commands.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/localqueue/failure.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/localqueue/paths.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/localqueue/queue.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/localqueue/retry/__init__.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/localqueue/retry/stores/__init__.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/localqueue/retry/stores/_shared.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/localqueue/retry/stores/base.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/localqueue/retry/stores/lmdb.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/localqueue/retry/stores/memory.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/localqueue/retry/stores/sqlite.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/localqueue/retry/tenacity.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/localqueue/services/__init__.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/localqueue/services/queue_worker.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/localqueue/stores/__init__.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/localqueue/stores/_shared.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/localqueue/stores/base.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/localqueue/stores/lmdb.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/localqueue/stores/memory.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/localqueue/stores/sqlite.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/localqueue/worker.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/tests/test_process_harness.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/tests/test_queue.py +0 -0
- {localqueue-0.3.4 → localqueue-0.4.0}/tests/test_retry.py +0 -0
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.0](https://github.com/brunoportis/localqueue/compare/v0.3.4...v0.4.0) (2026-04-24)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add release-please automation and CLI --version option ([f8db976](https://github.com/brunoportis/localqueue/commit/f8db9767f45fc1eb5055ef0e7eb946aae3af3129))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **release:** target uv.lock version explicitly ([3e71f44](https://github.com/brunoportis/localqueue/commit/3e71f4450e50de69488b18c04b018a6af00bf962))
|
|
14
|
+
* **release:** target uv.lock version explicitly ([31b45ce](https://github.com/brunoportis/localqueue/commit/31b45ce2ba3deea070d8d49c8bebebadcbc9fb37))
|
|
15
|
+
* **release:** track uv.lock in release-please ([897b86a](https://github.com/brunoportis/localqueue/commit/897b86aa33d9b38cba82da7b0e2623473488b94d))
|
|
16
|
+
* **release:** track uv.lock in release-please ([637ef7f](https://github.com/brunoportis/localqueue/commit/637ef7fca945f1153b643f27b0d6df6ccc39f778))
|
|
17
|
+
* **workflow:** configure git identity for release sync ([9872e7a](https://github.com/brunoportis/localqueue/commit/9872e7a85d1382a968f344444009e721da926e50))
|
|
18
|
+
* **workflow:** configure git identity for release sync ([2ef15fd](https://github.com/brunoportis/localqueue/commit/2ef15fdd3b5a877569b46980ef795442c1f3089c))
|
|
19
|
+
* **workflow:** sync uv.lock in release-please ([f26b1fa](https://github.com/brunoportis/localqueue/commit/f26b1fac2603f5ae0016ce994f5065291b6a6ad0))
|
|
20
|
+
* **workflow:** sync uv.lock in release-please ([193b4c6](https://github.com/brunoportis/localqueue/commit/193b4c6d5dba208ec43d7ea6ada402c5c4f1c2a5))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Documentation
|
|
24
|
+
|
|
25
|
+
* **use-cases:** simplify CLI examples ([84b92f2](https://github.com/brunoportis/localqueue/commit/84b92f2a420ddeb46dc8db2e6c1493fc45430bff))
|
|
26
|
+
|
|
3
27
|
## 0.3.4
|
|
4
28
|
|
|
5
29
|
- 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
|
+
Version: 0.4.0
|
|
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/
|
|
@@ -61,6 +61,8 @@ def send_email(job: dict[str, str]) -> None:
|
|
|
61
61
|
|
|
62
62
|
Full docs live at [brunoportis.github.io/localqueue](https://brunoportis.github.io/localqueue/). The source docs are in [`docs/`](docs/).
|
|
63
63
|
|
|
64
|
+
For development and release workflow details, see [`docs/develop.md`](docs/develop.md).
|
|
65
|
+
|
|
64
66
|
## Install
|
|
65
67
|
|
|
66
68
|
```bash
|
|
@@ -26,6 +26,8 @@ def send_email(job: dict[str, str]) -> None:
|
|
|
26
26
|
|
|
27
27
|
Full docs live at [brunoportis.github.io/localqueue](https://brunoportis.github.io/localqueue/). The source docs are in [`docs/`](docs/).
|
|
28
28
|
|
|
29
|
+
For development and release workflow details, see [`docs/develop.md`](docs/develop.md).
|
|
30
|
+
|
|
29
31
|
## Install
|
|
30
32
|
|
|
31
33
|
```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.
|
|
@@ -65,10 +65,12 @@ See [Compare](compare.md) for a short decision guide.
|
|
|
65
65
|
|
|
66
66
|
## Read more
|
|
67
67
|
|
|
68
|
+
- [Use cases](use-cases.md)
|
|
68
69
|
- [Persistent queues](queues.md)
|
|
69
70
|
- [Persistent retries](retries.md)
|
|
70
71
|
- [Stability](stability.md)
|
|
71
72
|
- [Compare](compare.md)
|
|
72
73
|
- [API reference](api.md)
|
|
73
74
|
- [Operational maturity](operational-maturity.md)
|
|
75
|
+
- [Development guide](develop.md)
|
|
74
76
|
- [Release checklist](release.md)
|
|
@@ -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`.
|
|
@@ -0,0 +1,174 @@
|
|
|
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.
|
|
@@ -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,
|
|
@@ -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
|
|
localqueue-0.3.4/docs/release.md
DELETED
|
@@ -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
|
|
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
|