localqueue 0.1.1__tar.gz → 0.2.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 (27) hide show
  1. {localqueue-0.1.1 → localqueue-0.2.0}/CHANGELOG.md +10 -0
  2. {localqueue-0.1.1 → localqueue-0.2.0}/PKG-INFO +9 -1
  3. {localqueue-0.1.1 → localqueue-0.2.0}/README.md +8 -0
  4. {localqueue-0.1.1 → localqueue-0.2.0}/docs/index.md +21 -0
  5. {localqueue-0.1.1 → localqueue-0.2.0}/docs/queues.md +26 -0
  6. {localqueue-0.1.1 → localqueue-0.2.0}/docs/release.md +1 -1
  7. localqueue-0.2.0/examples/process_webhook.sh +10 -0
  8. {localqueue-0.1.1 → localqueue-0.2.0}/localqueue/cli.py +104 -14
  9. {localqueue-0.1.1 → localqueue-0.2.0}/pyproject.toml +1 -1
  10. {localqueue-0.1.1 → localqueue-0.2.0}/tests/test_cli.py +125 -0
  11. {localqueue-0.1.1 → localqueue-0.2.0}/uv.lock +1 -1
  12. {localqueue-0.1.1 → localqueue-0.2.0}/.gitignore +0 -0
  13. {localqueue-0.1.1 → localqueue-0.2.0}/LICENSE +0 -0
  14. {localqueue-0.1.1 → localqueue-0.2.0}/docs/api.md +0 -0
  15. {localqueue-0.1.1 → localqueue-0.2.0}/docs/operational-maturity.md +0 -0
  16. {localqueue-0.1.1 → localqueue-0.2.0}/docs/retries.md +0 -0
  17. {localqueue-0.1.1 → localqueue-0.2.0}/examples/email_worker.py +0 -0
  18. {localqueue-0.1.1 → localqueue-0.2.0}/examples/enqueue_email.py +0 -0
  19. {localqueue-0.1.1 → localqueue-0.2.0}/localqueue/__init__.py +0 -0
  20. {localqueue-0.1.1 → localqueue-0.2.0}/localqueue/queue.py +0 -0
  21. {localqueue-0.1.1 → localqueue-0.2.0}/localqueue/retry/__init__.py +0 -0
  22. {localqueue-0.1.1 → localqueue-0.2.0}/localqueue/retry/store.py +0 -0
  23. {localqueue-0.1.1 → localqueue-0.2.0}/localqueue/retry/tenacity.py +0 -0
  24. {localqueue-0.1.1 → localqueue-0.2.0}/localqueue/store.py +0 -0
  25. {localqueue-0.1.1 → localqueue-0.2.0}/localqueue/worker.py +0 -0
  26. {localqueue-0.1.1 → localqueue-0.2.0}/tests/test_queue.py +0 -0
  27. {localqueue-0.1.1 → localqueue-0.2.0}/tests/test_retry.py +0 -0
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.2.0
6
+
7
+ - Add `queue stats --watch` for monitoring queue counts while workers run.
8
+ - Add `queue dead --watch` for repeatedly listing dead letters.
9
+ - Add `queue requeue-dead --all` for bulk recovery after a fix.
10
+ - Add structured `command not found` handling for `queue exec`.
11
+ - Add `examples/process_webhook.sh` as a shell/curl worker example.
12
+
3
13
  ## 0.1.1
4
14
 
5
15
  - Add project URLs for PyPI metadata.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: localqueue
3
- Version: 0.1.1
3
+ Version: 0.2.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/
@@ -125,9 +125,12 @@ localqueue queue add emails --value '{"to":"user@example.com"}'
125
125
  echo '{"to":"user@example.com"}' | localqueue queue add emails
126
126
  localqueue queue size emails
127
127
  localqueue queue stats emails
128
+ localqueue queue stats emails --watch --interval 1
128
129
  localqueue queue inspect emails <message-id>
129
130
  localqueue queue dead emails
131
+ localqueue queue dead emails --watch --interval 1
130
132
  localqueue queue requeue-dead emails <message-id>
133
+ localqueue queue requeue-dead emails --all
131
134
  localqueue queue pop emails --worker-id worker-1
132
135
  localqueue queue ack emails <message-id>
133
136
  localqueue queue exec emails -- python scripts/send_email.py
@@ -161,6 +164,7 @@ retry, release, and dead-letter rules as `queue process`.
161
164
  localqueue queue exec emails -- python scripts/send_email.py
162
165
  localqueue queue exec webhooks -- curl -X POST https://example.com/hook -d @-
163
166
  localqueue queue exec emails -- sh -c 'jq -r .to | xargs -I{} curl https://example.com/{}'
167
+ localqueue queue exec webhooks -- sh examples/process_webhook.sh
164
168
  ```
165
169
 
166
170
  Command output is captured so the CLI can keep printing its own JSON status.
@@ -284,6 +288,10 @@ Use `queue stats` when you need ready, delayed, inflight, dead-letter, and total
284
288
  counts instead of only ready messages. Use `queue inspect` to inspect one
285
289
  message by id, `queue dead` to list dead-letter messages, and
286
290
  `queue requeue-dead` to retry one after the failure cause is fixed.
291
+ Use `queue stats --watch` to print those counts repeatedly while local workers
292
+ are running.
293
+ Use `queue dead --watch` to keep an eye on newly failed jobs, and
294
+ `queue requeue-dead --all` once you have fixed the underlying issue.
287
295
 
288
296
  ## Operational boundaries
289
297
 
@@ -90,9 +90,12 @@ localqueue queue add emails --value '{"to":"user@example.com"}'
90
90
  echo '{"to":"user@example.com"}' | localqueue queue add emails
91
91
  localqueue queue size emails
92
92
  localqueue queue stats emails
93
+ localqueue queue stats emails --watch --interval 1
93
94
  localqueue queue inspect emails <message-id>
94
95
  localqueue queue dead emails
96
+ localqueue queue dead emails --watch --interval 1
95
97
  localqueue queue requeue-dead emails <message-id>
98
+ localqueue queue requeue-dead emails --all
96
99
  localqueue queue pop emails --worker-id worker-1
97
100
  localqueue queue ack emails <message-id>
98
101
  localqueue queue exec emails -- python scripts/send_email.py
@@ -126,6 +129,7 @@ retry, release, and dead-letter rules as `queue process`.
126
129
  localqueue queue exec emails -- python scripts/send_email.py
127
130
  localqueue queue exec webhooks -- curl -X POST https://example.com/hook -d @-
128
131
  localqueue queue exec emails -- sh -c 'jq -r .to | xargs -I{} curl https://example.com/{}'
132
+ localqueue queue exec webhooks -- sh examples/process_webhook.sh
129
133
  ```
130
134
 
131
135
  Command output is captured so the CLI can keep printing its own JSON status.
@@ -249,6 +253,10 @@ Use `queue stats` when you need ready, delayed, inflight, dead-letter, and total
249
253
  counts instead of only ready messages. Use `queue inspect` to inspect one
250
254
  message by id, `queue dead` to list dead-letter messages, and
251
255
  `queue requeue-dead` to retry one after the failure cause is fixed.
256
+ Use `queue stats --watch` to print those counts repeatedly while local workers
257
+ are running.
258
+ Use `queue dead --watch` to keep an eye on newly failed jobs, and
259
+ `queue requeue-dead --all` once you have fixed the underlying issue.
252
260
 
253
261
  ## Operational boundaries
254
262
 
@@ -64,6 +64,27 @@ localqueue queue process emails myapp.workers:send_email \
64
64
  --max-tries 5
65
65
  ```
66
66
 
67
+ Watch queue counts while workers run:
68
+
69
+ ```bash
70
+ localqueue queue stats emails --watch --interval 1
71
+ ```
72
+
73
+ For shell workers, `examples/process_webhook.sh` shows the same pattern with
74
+ `jq` and `curl`.
75
+
76
+ Inspect dead letters while workers are running:
77
+
78
+ ```bash
79
+ localqueue queue dead emails --watch --interval 2
80
+ ```
81
+
82
+ After fixing a problem, requeue all dead letters with:
83
+
84
+ ```bash
85
+ localqueue queue requeue-dead emails --all
86
+ ```
87
+
67
88
  For a local smoke test, enqueue one job and process it with the bundled example
68
89
  handler:
69
90
 
@@ -91,6 +91,7 @@ external command instead of an importable Python function.
91
91
  localqueue queue exec jobs -- python scripts/process_job.py
92
92
  localqueue queue exec webhooks -- curl -X POST https://example.com/hook -d @-
93
93
  localqueue queue exec jobs -- sh -c 'jq -r .id | xargs -I{} ./process-job {}'
94
+ localqueue queue exec webhooks -- sh examples/process_webhook.sh
94
95
  ```
95
96
 
96
97
  The command receives `message.value` on stdin as JSON. `localqueue` captures the
@@ -111,6 +112,8 @@ Command failures are stored in `last_error` with structured fields:
111
112
 
112
113
  Commands are executed without an implicit shell. Use `sh -c` explicitly when you
113
114
  want pipes, redirects, shell expansion, or multiple commands.
115
+ See `examples/process_webhook.sh` for a shell worker that reads JSON, extracts
116
+ fields with `jq`, and posts them with `curl`.
114
117
 
115
118
  ## Queue-style usage
116
119
 
@@ -315,3 +318,26 @@ by id without changing state. `dead_letters()` lists dead-letter messages for
315
318
  inspection. `requeue_dead()` moves a dead-letter message back to the ready queue.
316
319
  `purge()` removes all records for that queue, including ready, inflight, and
317
320
  dead-letter records.
321
+
322
+ From the CLI, use `queue stats --watch` to monitor those counts while workers
323
+ are running:
324
+
325
+ ```bash
326
+ localqueue queue stats jobs --watch --interval 1
327
+ ```
328
+
329
+ Each sample is printed as JSON with `ready`, `delayed`, `inflight`, `dead`, and
330
+ `total` counts. Stop the watch with `Ctrl-C`.
331
+
332
+ Use `queue dead --watch` to keep an eye on recent failures:
333
+
334
+ ```bash
335
+ localqueue queue dead jobs --watch --interval 2
336
+ ```
337
+
338
+ When the underlying problem is fixed, `queue requeue-dead --all` moves every
339
+ dead letter back to ready delivery:
340
+
341
+ ```bash
342
+ localqueue queue requeue-dead jobs --all
343
+ ```
@@ -104,4 +104,4 @@ Before `1.0.0`, use this convention:
104
104
 
105
105
  ## Current Release
106
106
 
107
- The current planned release is `0.1.1`.
107
+ The current planned release is `0.2.0`.
@@ -0,0 +1,10 @@
1
+ #!/bin/sh
2
+ set -eu
3
+
4
+ payload="$(cat)"
5
+ url="$(printf '%s' "$payload" | jq -r '.url')"
6
+ body="$(printf '%s' "$payload" | jq -c '.payload')"
7
+
8
+ curl -fsS -X POST "$url" \
9
+ -H 'content-type: application/json' \
10
+ -d "$body"
@@ -67,6 +67,17 @@ class _CommandExecutionError(RuntimeError):
67
67
  )
68
68
 
69
69
 
70
+ class _CommandNotFoundError(_CommandExecutionError):
71
+ def __init__(self, command: list[str]) -> None:
72
+ super().__init__(
73
+ command,
74
+ exit_code=127,
75
+ stdout="",
76
+ stderr="command not found",
77
+ )
78
+ self.args = (f"command not found: {_format_command(command)}",)
79
+
80
+
70
81
  def main(args: list[str] | None = None) -> None:
71
82
  try:
72
83
  from rich.console import Console
@@ -251,29 +262,65 @@ def _build_app(typer: Any, yaml: Any, console: Any, err_console: Any) -> Any:
251
262
  def queue_stats(
252
263
  queue: str,
253
264
  store_path: str | None = typer.Option(None, "--store-path"),
265
+ watch: bool = typer.Option(False, "--watch"),
266
+ interval: float = typer.Option(1.0, "--interval", min=0.001),
254
267
  ) -> None:
255
- stats = _queue(queue, _resolve_store_path(store_path, config)).stats()
256
- _print_json(console, stats.as_dict())
268
+ persistent_queue = _queue(queue, _resolve_store_path(store_path, config))
269
+ with _shutdown_state() as shutdown:
270
+ _print_queue_stats(
271
+ persistent_queue,
272
+ console=console,
273
+ watch=watch,
274
+ interval=interval,
275
+ shutdown=shutdown,
276
+ )
257
277
 
258
278
  @queue_app.command("dead")
259
279
  def queue_dead(
260
280
  queue: str,
261
281
  store_path: str | None = typer.Option(None, "--store-path"),
262
282
  limit: int | None = typer.Option(None, "--limit", min=0),
283
+ watch: bool = typer.Option(False, "--watch"),
284
+ interval: float = typer.Option(1.0, "--interval", min=0.001),
263
285
  ) -> None:
264
- messages = _queue(queue, _resolve_store_path(store_path, config)).dead_letters(
265
- limit=limit
266
- )
267
- _print_json(console, [_message_payload(message) for message in messages])
286
+ persistent_queue = _queue(queue, _resolve_store_path(store_path, config))
287
+ with _shutdown_state() as shutdown:
288
+ _print_dead_letters(
289
+ persistent_queue,
290
+ console=console,
291
+ limit=limit,
292
+ watch=watch,
293
+ interval=interval,
294
+ shutdown=shutdown,
295
+ )
268
296
 
269
297
  @queue_app.command("requeue-dead")
270
298
  def queue_requeue_dead(
271
299
  queue: str,
272
- message_id: str,
300
+ message_id: str | None = typer.Argument(None),
273
301
  store_path: str | None = typer.Option(None, "--store-path"),
274
302
  delay: float = typer.Option(0.0, "--delay", min=0.0),
303
+ all_: bool = typer.Option(False, "--all"),
275
304
  ) -> None:
276
305
  persistent_queue = _queue(queue, _resolve_store_path(store_path, config))
306
+ if all_:
307
+ if message_id is not None:
308
+ err_console.print(
309
+ "[red]pass either a message id or --all, not both[/red]"
310
+ )
311
+ raise typer.Exit(1)
312
+ messages = persistent_queue.dead_letters()
313
+ requeued = 0
314
+ for message in messages:
315
+ if persistent_queue.requeue_dead(message, delay=delay):
316
+ requeued += 1
317
+ _print_json(console, {"requeued": requeued})
318
+ return
319
+ if message_id is None:
320
+ err_console.print(
321
+ "[red]pass a message id or use --all to requeue dead letters[/red]"
322
+ )
323
+ raise typer.Exit(1)
277
324
  message = QueueMessage(id=message_id, value=None, queue=queue)
278
325
  if not persistent_queue.requeue_dead(message, delay=delay):
279
326
  err_console.print(f"[red]dead-letter message not found:[/red] {message_id}")
@@ -550,6 +597,46 @@ def _process_queue_messages(
550
597
  return 0
551
598
 
552
599
 
600
+ def _print_dead_letters(
601
+ queue: PersistentQueue,
602
+ *,
603
+ console: Any,
604
+ limit: int | None,
605
+ watch: bool,
606
+ interval: float,
607
+ shutdown: _ShutdownState,
608
+ ) -> None:
609
+ while True:
610
+ messages = queue.dead_letters(limit=limit)
611
+ _print_json(console, [_message_payload(message) for message in messages])
612
+ if not watch:
613
+ return
614
+ if shutdown.requested:
615
+ return
616
+ time.sleep(interval)
617
+ if shutdown.requested:
618
+ return
619
+
620
+
621
+ def _print_queue_stats(
622
+ queue: PersistentQueue,
623
+ *,
624
+ console: Any,
625
+ watch: bool,
626
+ interval: float,
627
+ shutdown: _ShutdownState,
628
+ ) -> None:
629
+ while True:
630
+ _print_json(console, queue.stats().as_dict())
631
+ if not watch:
632
+ return
633
+ if shutdown.requested:
634
+ return
635
+ time.sleep(interval)
636
+ if shutdown.requested:
637
+ return
638
+
639
+
553
640
  def _poll_timeout(forever: bool, block: bool, timeout: float | None) -> float | None:
554
641
  if forever and block and timeout is None:
555
642
  return 0.5
@@ -692,13 +779,16 @@ def _command_handler(command: list[str]) -> Callable[[Any], None]:
692
779
  raise ValueError("command cannot be empty")
693
780
 
694
781
  def run_command(value: Any) -> None:
695
- completed = subprocess.run(
696
- command,
697
- input=_json_dumps(value) + "\n",
698
- capture_output=True,
699
- text=True,
700
- check=False,
701
- )
782
+ try:
783
+ completed = subprocess.run(
784
+ command,
785
+ input=_json_dumps(value) + "\n",
786
+ capture_output=True,
787
+ text=True,
788
+ check=False,
789
+ )
790
+ except FileNotFoundError as exc:
791
+ raise _CommandNotFoundError(command) from exc
702
792
  if completed.returncode != 0:
703
793
  raise _CommandExecutionError(
704
794
  command,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "localqueue"
3
- version = "0.1.1"
3
+ version = "0.2.0"
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"
@@ -28,6 +28,8 @@ from localqueue.cli import (
28
28
  _load_callable,
29
29
  _load_config,
30
30
  _parse_json,
31
+ _print_dead_letters,
32
+ _print_queue_stats,
31
33
  _process_message,
32
34
  _process_queue_messages,
33
35
  _read_value,
@@ -380,6 +382,30 @@ class CliTests(unittest.TestCase):
380
382
  self.assertEqual(missing_requeue.exit_code, 1)
381
383
  self.assertIn("dead-letter message not found", missing_requeue.output)
382
384
 
385
+ def test_print_dead_letters_watch_stops_on_shutdown(self) -> None:
386
+ queue = PersistentQueue("emails", store=MemoryQueueStore())
387
+ _ = queue.put({"to": "user@example.com"})
388
+ message = queue.get_message()
389
+ self.assertTrue(queue.dead_letter(message, error=RuntimeError("bad")))
390
+ console = _JsonConsole()
391
+ shutdown = _ShutdownState()
392
+
393
+ def request_shutdown(_interval: float) -> None:
394
+ shutdown.requested = True
395
+
396
+ with mock.patch("time.sleep", side_effect=request_shutdown):
397
+ _print_dead_letters(
398
+ queue,
399
+ console=console,
400
+ limit=None,
401
+ watch=True,
402
+ interval=0.001,
403
+ shutdown=shutdown,
404
+ )
405
+
406
+ self.assertEqual(len(console.values), 1)
407
+ self.assertEqual(console.values[0][0]["id"], message.id)
408
+
383
409
  def test_queue_process_command_acks_successful_handler(self) -> None:
384
410
  with tempfile.TemporaryDirectory() as tmpdir:
385
411
  tmp_path = Path(tmpdir)
@@ -496,6 +522,40 @@ class CliTests(unittest.TestCase):
496
522
  self.assertEqual(result.exit_code, 1)
497
523
  self.assertIn("module:function", result.output)
498
524
 
525
+ def test_queue_exec_reports_missing_command(self) -> None:
526
+ with tempfile.TemporaryDirectory() as tmpdir:
527
+ store_path = str(Path(tmpdir) / "queues")
528
+ retry_store_path = str(Path(tmpdir) / "retries.sqlite3")
529
+ queue = PersistentQueue("emails", store_path=store_path)
530
+ message = queue.put({"to": "user@example.com"})
531
+
532
+ result = self._invoke(
533
+ [
534
+ "queue",
535
+ "exec",
536
+ "emails",
537
+ "--store-path",
538
+ store_path,
539
+ "--retry-store-path",
540
+ retry_store_path,
541
+ "--max-tries",
542
+ "1",
543
+ "--",
544
+ "definitely-not-a-real-command-123",
545
+ ]
546
+ )
547
+
548
+ self.assertEqual(result.exit_code, 1)
549
+ payload = json.loads(result.stdout)
550
+ self.assertEqual(payload["id"], message.id)
551
+ self.assertEqual(payload["state"], "failed")
552
+ self.assertEqual(payload["last_error"]["type"], "_CommandNotFoundError")
553
+ self.assertEqual(payload["last_error"]["exit_code"], 127)
554
+ self.assertEqual(
555
+ payload["last_error"]["command"], ["definitely-not-a-real-command-123"]
556
+ )
557
+ self.assertIn("command not found", payload["last_error"]["message"])
558
+
499
559
  def test_command_handler_passes_message_value_as_json_stdin(self) -> None:
500
560
  with tempfile.TemporaryDirectory() as tmpdir:
501
561
  tmp_path = Path(tmpdir)
@@ -610,6 +670,48 @@ class CliTests(unittest.TestCase):
610
670
  self.assertEqual(dead_letters[0].last_error["exit_code"], 7)
611
671
  self.assertEqual(dead_letters[0].last_error["stderr"], "cannot deliver")
612
672
 
673
+ def test_queue_requeue_dead_all_moves_every_dead_message(self) -> None:
674
+ with tempfile.TemporaryDirectory() as tmpdir:
675
+ store_path = str(Path(tmpdir) / "queues")
676
+ persistent_queue = PersistentQueue("emails", store_path=store_path)
677
+ first = persistent_queue.put({"to": "one@example.com"})
678
+ second = persistent_queue.put({"to": "two@example.com"})
679
+ self.assertTrue(
680
+ persistent_queue.dead_letter(
681
+ persistent_queue.get_message(), error=RuntimeError("bad 1")
682
+ )
683
+ )
684
+ self.assertTrue(
685
+ persistent_queue.dead_letter(
686
+ persistent_queue.get_message(), error=RuntimeError("bad 2")
687
+ )
688
+ )
689
+
690
+ result = self._invoke(
691
+ [
692
+ "queue",
693
+ "requeue-dead",
694
+ "emails",
695
+ "--all",
696
+ "--store-path",
697
+ store_path,
698
+ ]
699
+ )
700
+
701
+ self.assertEqual(result.exit_code, 0, result.output)
702
+ self.assertEqual(json.loads(result.stdout), {"requeued": 2})
703
+ fresh_queue = PersistentQueue("emails", store_path=store_path)
704
+ self.assertEqual(fresh_queue.dead_letters(), [])
705
+ self.assertEqual(fresh_queue.qsize(), 2)
706
+ first_message = fresh_queue.inspect(first.id)
707
+ second_message = fresh_queue.inspect(second.id)
708
+ self.assertIsNotNone(first_message)
709
+ self.assertIsNotNone(second_message)
710
+ assert first_message is not None
711
+ assert second_message is not None
712
+ self.assertEqual(first_message.state, "ready")
713
+ self.assertEqual(second_message.state, "ready")
714
+
613
715
  def test_process_message_acks_successful_handler(self) -> None:
614
716
  queue = PersistentQueue("emails", store=MemoryQueueStore())
615
717
  _ = queue.put({"to": "user@example.com"})
@@ -724,6 +826,29 @@ class CliTests(unittest.TestCase):
724
826
  self.assertEqual(console.values, [])
725
827
  self.assertEqual(err_console.messages, ["[yellow]queue is empty[/yellow]"])
726
828
 
829
+ def test_print_queue_stats_watch_stops_on_shutdown(self) -> None:
830
+ queue = PersistentQueue("emails", store=MemoryQueueStore())
831
+ _ = queue.put({"to": "user@example.com"})
832
+ console = _JsonConsole()
833
+ shutdown = _ShutdownState()
834
+
835
+ def request_shutdown(_interval: float) -> None:
836
+ shutdown.requested = True
837
+
838
+ with mock.patch("time.sleep", side_effect=request_shutdown):
839
+ _print_queue_stats(
840
+ queue,
841
+ console=console,
842
+ watch=True,
843
+ interval=0.001,
844
+ shutdown=shutdown,
845
+ )
846
+
847
+ self.assertEqual(
848
+ console.values,
849
+ [{"ready": 1, "delayed": 0, "inflight": 0, "dead": 0, "total": 1}],
850
+ )
851
+
727
852
  def test_process_queue_messages_forever_stops_after_current_message(self) -> None:
728
853
  queue = PersistentQueue("emails", store=MemoryQueueStore())
729
854
  _ = queue.put({"to": "first@example.com"})
@@ -200,7 +200,7 @@ wheels = [
200
200
 
201
201
  [[package]]
202
202
  name = "localqueue"
203
- version = "0.1.1"
203
+ version = "0.2.0"
204
204
  source = { editable = "." }
205
205
  dependencies = [
206
206
  { name = "tenacity" },
File without changes
File without changes
File without changes
File without changes