localqueue 0.3.0__py3-none-any.whl → 0.3.2__py3-none-any.whl
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/__init__.py +2 -0
- localqueue/cli.py +339 -116
- localqueue/failure.py +6 -14
- localqueue/paths.py +23 -0
- localqueue/queue.py +19 -16
- localqueue/retry/__init__.py +2 -0
- localqueue/retry/store.py +83 -31
- localqueue/retry/tenacity.py +111 -31
- localqueue/store.py +214 -97
- localqueue/worker.py +24 -16
- {localqueue-0.3.0.dist-info → localqueue-0.3.2.dist-info}/METADATA +9 -2
- localqueue-0.3.2.dist-info/RECORD +15 -0
- localqueue-0.3.0.dist-info/RECORD +0 -14
- {localqueue-0.3.0.dist-info → localqueue-0.3.2.dist-info}/WHEEL +0 -0
- {localqueue-0.3.0.dist-info → localqueue-0.3.2.dist-info}/entry_points.txt +0 -0
- {localqueue-0.3.0.dist-info → localqueue-0.3.2.dist-info}/licenses/LICENSE +0 -0
localqueue/__init__.py
CHANGED
|
@@ -16,6 +16,7 @@ from .retry import (
|
|
|
16
16
|
PersistentRetrying,
|
|
17
17
|
RetryRecord,
|
|
18
18
|
SQLiteAttemptStore,
|
|
19
|
+
close_default_store,
|
|
19
20
|
configure_default_store,
|
|
20
21
|
configure_default_store_factory,
|
|
21
22
|
idempotency_key_from_id,
|
|
@@ -55,6 +56,7 @@ __all__ = [
|
|
|
55
56
|
"SQLiteAttemptStore",
|
|
56
57
|
"SQLiteQueueStore",
|
|
57
58
|
"__version__",
|
|
59
|
+
"close_default_store",
|
|
58
60
|
"configure_default_store",
|
|
59
61
|
"configure_default_store_factory",
|
|
60
62
|
"idempotency_key_from_id",
|
localqueue/cli.py
CHANGED
|
@@ -8,17 +8,16 @@ import subprocess
|
|
|
8
8
|
import sys
|
|
9
9
|
import time
|
|
10
10
|
from collections import Counter
|
|
11
|
-
from collections.abc import Callable
|
|
12
11
|
from contextlib import contextmanager
|
|
13
12
|
from dataclasses import dataclass
|
|
14
13
|
from pathlib import Path
|
|
15
14
|
from queue import Empty
|
|
16
|
-
from
|
|
17
|
-
from typing import Any, Iterator
|
|
15
|
+
from typing import Any, Iterator, TYPE_CHECKING
|
|
18
16
|
|
|
19
17
|
from tenacity import wait_none
|
|
20
18
|
|
|
21
19
|
from .failure import is_permanent_failure
|
|
20
|
+
from .paths import default_queue_store_path, default_retry_store_path
|
|
22
21
|
from .queue import PersistentQueue
|
|
23
22
|
from .retry import (
|
|
24
23
|
PersistentRetryExhausted,
|
|
@@ -34,8 +33,12 @@ from .worker import (
|
|
|
34
33
|
_sleep_for_policy,
|
|
35
34
|
)
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from types import FrameType
|
|
38
|
+
from collections.abc import Callable
|
|
39
|
+
|
|
40
|
+
DEFAULT_STORE_PATH = str(default_queue_store_path())
|
|
41
|
+
DEFAULT_RETRY_STORE_PATH = str(default_retry_store_path())
|
|
39
42
|
CONFIG_FILENAME = "config.yaml"
|
|
40
43
|
|
|
41
44
|
|
|
@@ -113,7 +116,20 @@ def _build_app(typer: Any, yaml: Any, console: Any, err_console: Any) -> Any:
|
|
|
113
116
|
app.add_typer(queue_app, name="queue")
|
|
114
117
|
app.add_typer(retry_app, name="retry")
|
|
115
118
|
app.add_typer(config_app, name="config")
|
|
119
|
+
_register_config_commands(config_app, typer, yaml, console, err_console, config)
|
|
120
|
+
_register_queue_commands(queue_app, typer, console, err_console, config)
|
|
121
|
+
_register_retry_commands(retry_app, typer, console, err_console, config)
|
|
122
|
+
return app
|
|
116
123
|
|
|
124
|
+
|
|
125
|
+
def _register_config_commands(
|
|
126
|
+
config_app: Any,
|
|
127
|
+
typer: Any,
|
|
128
|
+
yaml: Any,
|
|
129
|
+
console: Any,
|
|
130
|
+
err_console: Any,
|
|
131
|
+
config: dict[str, Any],
|
|
132
|
+
) -> None:
|
|
117
133
|
@config_app.command("path")
|
|
118
134
|
def config_path() -> None:
|
|
119
135
|
console.print(str(_config_path()))
|
|
@@ -124,7 +140,7 @@ def _build_app(typer: Any, yaml: Any, console: Any, err_console: Any) -> Any:
|
|
|
124
140
|
|
|
125
141
|
@config_app.command("init")
|
|
126
142
|
def config_init(
|
|
127
|
-
store_path: str = typer.Option(
|
|
143
|
+
store_path: str | None = typer.Option(None, "--store-path"),
|
|
128
144
|
retry_store_path: str | None = typer.Option(None, "--retry-store-path"),
|
|
129
145
|
dead_letter_ttl_seconds: float | None = typer.Option(
|
|
130
146
|
None, "--dead-letter-ttl-seconds", min=0.0
|
|
@@ -141,7 +157,7 @@ def _build_app(typer: Any, yaml: Any, console: Any, err_console: Any) -> Any:
|
|
|
141
157
|
)
|
|
142
158
|
raise typer.Exit(1)
|
|
143
159
|
|
|
144
|
-
new_config: dict[str, Any] = {"store_path": store_path}
|
|
160
|
+
new_config: dict[str, Any] = {"store_path": _resolve_store_path(store_path, {})}
|
|
145
161
|
if retry_store_path is not None:
|
|
146
162
|
new_config["retry_store_path"] = retry_store_path
|
|
147
163
|
if dead_letter_ttl_seconds is not None:
|
|
@@ -180,6 +196,26 @@ def _build_app(typer: Any, yaml: Any, console: Any, err_console: Any) -> Any:
|
|
|
180
196
|
config.update(updated)
|
|
181
197
|
_print_json(console, config)
|
|
182
198
|
|
|
199
|
+
|
|
200
|
+
def _register_queue_commands(
|
|
201
|
+
queue_app: Any,
|
|
202
|
+
typer: Any,
|
|
203
|
+
console: Any,
|
|
204
|
+
err_console: Any,
|
|
205
|
+
config: dict[str, Any],
|
|
206
|
+
) -> None:
|
|
207
|
+
_register_queue_basic_commands(queue_app, typer, console, err_console, config)
|
|
208
|
+
_register_queue_admin_commands(queue_app, typer, console, err_console, config)
|
|
209
|
+
_register_queue_worker_commands(queue_app, typer, console, err_console, config)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _register_queue_basic_commands(
|
|
213
|
+
queue_app: Any,
|
|
214
|
+
typer: Any,
|
|
215
|
+
console: Any,
|
|
216
|
+
err_console: Any,
|
|
217
|
+
config: dict[str, Any],
|
|
218
|
+
) -> None:
|
|
183
219
|
@queue_app.command("add")
|
|
184
220
|
def queue_add(
|
|
185
221
|
queue: str,
|
|
@@ -209,6 +245,17 @@ def _build_app(typer: Any, yaml: Any, console: Any, err_console: Any) -> Any:
|
|
|
209
245
|
)
|
|
210
246
|
_print_json(console, _message_payload(message))
|
|
211
247
|
|
|
248
|
+
_register_queue_message_commands(queue_app, typer, console, err_console, config)
|
|
249
|
+
_register_queue_maintenance_commands(queue_app, typer, console, err_console, config)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _register_queue_message_commands(
|
|
253
|
+
queue_app: Any,
|
|
254
|
+
typer: Any,
|
|
255
|
+
console: Any,
|
|
256
|
+
err_console: Any,
|
|
257
|
+
config: dict[str, Any],
|
|
258
|
+
) -> None:
|
|
212
259
|
@queue_app.command("pop")
|
|
213
260
|
def queue_pop(
|
|
214
261
|
queue: str,
|
|
@@ -318,6 +365,27 @@ def _build_app(typer: Any, yaml: Any, console: Any, err_console: Any) -> Any:
|
|
|
318
365
|
) -> None:
|
|
319
366
|
console.print(_queue(queue, _resolve_store_path(store_path, config)).qsize())
|
|
320
367
|
|
|
368
|
+
|
|
369
|
+
def _register_queue_maintenance_commands(
|
|
370
|
+
queue_app: Any,
|
|
371
|
+
typer: Any,
|
|
372
|
+
console: Any,
|
|
373
|
+
err_console: Any,
|
|
374
|
+
config: dict[str, Any],
|
|
375
|
+
) -> None:
|
|
376
|
+
_register_queue_stats_commands(queue_app, typer, console, err_console, config)
|
|
377
|
+
_register_queue_dead_commands(queue_app, typer, console, err_console, config)
|
|
378
|
+
_register_queue_requeue_commands(queue_app, typer, console, err_console, config)
|
|
379
|
+
_register_queue_purge_commands(queue_app, typer, console, err_console, config)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _register_queue_stats_commands(
|
|
383
|
+
queue_app: Any,
|
|
384
|
+
typer: Any,
|
|
385
|
+
console: Any,
|
|
386
|
+
err_console: Any,
|
|
387
|
+
config: dict[str, Any],
|
|
388
|
+
) -> None:
|
|
321
389
|
@queue_app.command("stats")
|
|
322
390
|
def queue_stats(
|
|
323
391
|
queue: str,
|
|
@@ -361,6 +429,14 @@ def _build_app(typer: Any, yaml: Any, console: Any, err_console: Any) -> Any:
|
|
|
361
429
|
},
|
|
362
430
|
)
|
|
363
431
|
|
|
432
|
+
|
|
433
|
+
def _register_queue_dead_commands(
|
|
434
|
+
queue_app: Any,
|
|
435
|
+
typer: Any,
|
|
436
|
+
console: Any,
|
|
437
|
+
err_console: Any,
|
|
438
|
+
config: dict[str, Any],
|
|
439
|
+
) -> None:
|
|
364
440
|
@queue_app.command("dead")
|
|
365
441
|
def queue_dead(
|
|
366
442
|
queue: str,
|
|
@@ -433,39 +509,14 @@ def _build_app(typer: Any, yaml: Any, console: Any, err_console: Any) -> Any:
|
|
|
433
509
|
failed_within=failed_within,
|
|
434
510
|
)
|
|
435
511
|
|
|
436
|
-
@retry_app.command("prune")
|
|
437
|
-
def retry_prune(
|
|
438
|
-
older_than: float | None = typer.Option(None, "--older-than", min=0.0),
|
|
439
|
-
retry_store_path: str | None = typer.Option(None, "--retry-store-path"),
|
|
440
|
-
dry_run: bool = typer.Option(False, "--dry-run"),
|
|
441
|
-
) -> None:
|
|
442
|
-
store = SQLiteAttemptStore(_resolve_retry_store_path(retry_store_path, config))
|
|
443
|
-
try:
|
|
444
|
-
resolved_older_than = _resolve_retry_record_ttl(older_than, config)
|
|
445
|
-
if resolved_older_than is None:
|
|
446
|
-
err_console.print(
|
|
447
|
-
"[red]pass --older-than or configure retry_record_ttl_seconds[/red]"
|
|
448
|
-
)
|
|
449
|
-
raise typer.Exit(1)
|
|
450
|
-
now = time.time()
|
|
451
|
-
deleted = (
|
|
452
|
-
store.count_exhausted_older_than(
|
|
453
|
-
older_than=resolved_older_than, now=now
|
|
454
|
-
)
|
|
455
|
-
if dry_run
|
|
456
|
-
else store.prune_exhausted(older_than=resolved_older_than, now=now)
|
|
457
|
-
)
|
|
458
|
-
finally:
|
|
459
|
-
store.close()
|
|
460
|
-
_print_json(
|
|
461
|
-
console,
|
|
462
|
-
{
|
|
463
|
-
"dry_run": dry_run,
|
|
464
|
-
"older_than": resolved_older_than,
|
|
465
|
-
"would_delete" if dry_run else "deleted": deleted,
|
|
466
|
-
},
|
|
467
|
-
)
|
|
468
512
|
|
|
513
|
+
def _register_queue_requeue_commands(
|
|
514
|
+
queue_app: Any,
|
|
515
|
+
typer: Any,
|
|
516
|
+
console: Any,
|
|
517
|
+
err_console: Any,
|
|
518
|
+
config: dict[str, Any],
|
|
519
|
+
) -> None:
|
|
469
520
|
@queue_app.command("requeue-dead")
|
|
470
521
|
def queue_requeue_dead(
|
|
471
522
|
queue: str,
|
|
@@ -517,6 +568,14 @@ def _build_app(typer: Any, yaml: Any, console: Any, err_console: Any) -> Any:
|
|
|
517
568
|
)
|
|
518
569
|
_print_json(console, {"id": message_id, "state": "requeued"})
|
|
519
570
|
|
|
571
|
+
|
|
572
|
+
def _register_queue_purge_commands(
|
|
573
|
+
queue_app: Any,
|
|
574
|
+
typer: Any,
|
|
575
|
+
console: Any,
|
|
576
|
+
err_console: Any,
|
|
577
|
+
config: dict[str, Any],
|
|
578
|
+
) -> None:
|
|
520
579
|
@queue_app.command("purge")
|
|
521
580
|
def queue_purge(
|
|
522
581
|
queue: str,
|
|
@@ -528,6 +587,55 @@ def _build_app(typer: Any, yaml: Any, console: Any, err_console: Any) -> Any:
|
|
|
528
587
|
_emit_event(err_console, "queue.purge", queue=queue, deleted=deleted)
|
|
529
588
|
console.print(deleted)
|
|
530
589
|
|
|
590
|
+
|
|
591
|
+
def _register_retry_commands(
|
|
592
|
+
retry_app: Any,
|
|
593
|
+
typer: Any,
|
|
594
|
+
console: Any,
|
|
595
|
+
err_console: Any,
|
|
596
|
+
config: dict[str, Any],
|
|
597
|
+
) -> None:
|
|
598
|
+
@retry_app.command("prune")
|
|
599
|
+
def retry_prune(
|
|
600
|
+
older_than: float | None = typer.Option(None, "--older-than", min=0.0),
|
|
601
|
+
retry_store_path: str | None = typer.Option(None, "--retry-store-path"),
|
|
602
|
+
dry_run: bool = typer.Option(False, "--dry-run"),
|
|
603
|
+
) -> None:
|
|
604
|
+
store = SQLiteAttemptStore(_resolve_retry_store_path(retry_store_path, config))
|
|
605
|
+
try:
|
|
606
|
+
resolved_older_than = _resolve_retry_record_ttl(older_than, config)
|
|
607
|
+
if resolved_older_than is None:
|
|
608
|
+
err_console.print(
|
|
609
|
+
"[red]pass --older-than or configure retry_record_ttl_seconds[/red]"
|
|
610
|
+
)
|
|
611
|
+
raise typer.Exit(1)
|
|
612
|
+
now = time.time()
|
|
613
|
+
deleted = (
|
|
614
|
+
store.count_exhausted_older_than(
|
|
615
|
+
older_than=resolved_older_than, now=now
|
|
616
|
+
)
|
|
617
|
+
if dry_run
|
|
618
|
+
else store.prune_exhausted(older_than=resolved_older_than, now=now)
|
|
619
|
+
)
|
|
620
|
+
finally:
|
|
621
|
+
store.close()
|
|
622
|
+
_print_json(
|
|
623
|
+
console,
|
|
624
|
+
{
|
|
625
|
+
"dry_run": dry_run,
|
|
626
|
+
"older_than": resolved_older_than,
|
|
627
|
+
"would_delete" if dry_run else "deleted": deleted,
|
|
628
|
+
},
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
def _register_queue_admin_commands(
|
|
633
|
+
queue_app: Any,
|
|
634
|
+
typer: Any,
|
|
635
|
+
console: Any,
|
|
636
|
+
err_console: Any,
|
|
637
|
+
config: dict[str, Any],
|
|
638
|
+
) -> None:
|
|
531
639
|
@queue_app.command("process")
|
|
532
640
|
def queue_process(
|
|
533
641
|
queue: str,
|
|
@@ -605,6 +713,14 @@ def _build_app(typer: Any, yaml: Any, console: Any, err_console: Any) -> Any:
|
|
|
605
713
|
if exit_code:
|
|
606
714
|
raise typer.Exit(exit_code)
|
|
607
715
|
|
|
716
|
+
|
|
717
|
+
def _register_queue_worker_commands(
|
|
718
|
+
queue_app: Any,
|
|
719
|
+
typer: Any,
|
|
720
|
+
console: Any,
|
|
721
|
+
err_console: Any,
|
|
722
|
+
config: dict[str, Any],
|
|
723
|
+
) -> None:
|
|
608
724
|
@queue_app.command("exec")
|
|
609
725
|
def queue_exec(
|
|
610
726
|
queue: str,
|
|
@@ -680,8 +796,6 @@ def _build_app(typer: Any, yaml: Any, console: Any, err_console: Any) -> Any:
|
|
|
680
796
|
if exit_code:
|
|
681
797
|
raise typer.Exit(exit_code)
|
|
682
798
|
|
|
683
|
-
return app
|
|
684
|
-
|
|
685
799
|
|
|
686
800
|
def _queue(
|
|
687
801
|
name: str, store_path: str, *, lease_timeout: float = 30.0
|
|
@@ -859,91 +973,200 @@ def _process_queue_messages(
|
|
|
859
973
|
resolved_policy = (
|
|
860
974
|
worker_policy if worker_policy is not None else PersistentWorkerConfig()
|
|
861
975
|
)
|
|
976
|
+
owned_retry_store: SQLiteAttemptStore | None = None
|
|
862
977
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
if
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
if worker_id is not None:
|
|
870
|
-
queue.record_worker_heartbeat(worker_id)
|
|
871
|
-
_sleep_for_policy(policy_state, resolved_policy)
|
|
978
|
+
try:
|
|
979
|
+
while forever or processed < max_jobs:
|
|
980
|
+
if shutdown.requested:
|
|
981
|
+
if forever:
|
|
982
|
+
_print_json(console, {"state": "stopped", "processed": processed})
|
|
983
|
+
return 0
|
|
872
984
|
|
|
873
|
-
|
|
874
|
-
|
|
985
|
+
iteration, owned_retry_store, empty_queue = _process_queue_iteration(
|
|
986
|
+
queue,
|
|
987
|
+
handler,
|
|
988
|
+
console=console,
|
|
989
|
+
err_console=err_console,
|
|
990
|
+
policy_state=policy_state,
|
|
991
|
+
resolved_policy=resolved_policy,
|
|
992
|
+
retry_store_path=retry_store_path,
|
|
993
|
+
owned_retry_store=owned_retry_store,
|
|
994
|
+
max_tries=max_tries,
|
|
995
|
+
worker_id=worker_id,
|
|
875
996
|
block=block,
|
|
876
|
-
timeout=
|
|
877
|
-
|
|
997
|
+
timeout=timeout,
|
|
998
|
+
idle_sleep=idle_sleep,
|
|
999
|
+
release_delay=release_delay,
|
|
1000
|
+
dead_letter_on_exhaustion=dead_letter_on_exhaustion,
|
|
1001
|
+
log_events=log_events,
|
|
1002
|
+
mode=mode,
|
|
1003
|
+
forever=forever,
|
|
878
1004
|
)
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
if not block:
|
|
882
|
-
time.sleep(idle_sleep)
|
|
883
|
-
continue
|
|
884
|
-
if processed == 0:
|
|
885
|
-
if err_console is not None:
|
|
1005
|
+
if empty_queue:
|
|
1006
|
+
if processed == 0 and err_console is not None:
|
|
886
1007
|
err_console.print("[yellow]queue is empty[/yellow]")
|
|
887
|
-
|
|
888
|
-
|
|
1008
|
+
if processed == 0:
|
|
1009
|
+
return 1
|
|
1010
|
+
break
|
|
1011
|
+
assert iteration is not None
|
|
1012
|
+
if _handle_processed_queue_result(
|
|
1013
|
+
queue,
|
|
1014
|
+
iteration.message,
|
|
1015
|
+
iteration.process_result,
|
|
1016
|
+
console=console,
|
|
1017
|
+
worker_id=worker_id,
|
|
1018
|
+
policy_state=policy_state,
|
|
1019
|
+
):
|
|
1020
|
+
processed += 1
|
|
1021
|
+
continue
|
|
1022
|
+
if _handle_failed_queue_result(
|
|
1023
|
+
queue,
|
|
1024
|
+
iteration.message,
|
|
1025
|
+
iteration.process_result,
|
|
1026
|
+
console=console,
|
|
1027
|
+
worker_id=worker_id,
|
|
1028
|
+
policy_state=policy_state,
|
|
1029
|
+
resolved_policy=resolved_policy,
|
|
1030
|
+
forever=forever,
|
|
1031
|
+
):
|
|
1032
|
+
continue
|
|
1033
|
+
return 1
|
|
889
1034
|
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
queue=message.queue,
|
|
895
|
-
message_id=message.id,
|
|
896
|
-
leased_by=message.leased_by,
|
|
897
|
-
attempts=message.attempts,
|
|
898
|
-
leased_until=message.leased_until,
|
|
899
|
-
)
|
|
1035
|
+
return 0
|
|
1036
|
+
finally:
|
|
1037
|
+
if owned_retry_store is not None:
|
|
1038
|
+
owned_retry_store.close()
|
|
900
1039
|
|
|
901
|
-
result = _process_message(
|
|
902
|
-
queue,
|
|
903
|
-
message,
|
|
904
|
-
handler,
|
|
905
|
-
retry_store_path=retry_store_path,
|
|
906
|
-
max_tries=max_tries,
|
|
907
|
-
release_delay=release_delay,
|
|
908
|
-
dead_letter_on_exhaustion=dead_letter_on_exhaustion,
|
|
909
|
-
log_events=log_events,
|
|
910
|
-
mode=mode,
|
|
911
|
-
err_console=err_console,
|
|
912
|
-
)
|
|
913
|
-
if result.processed:
|
|
914
|
-
processed += 1
|
|
915
|
-
_record_success(policy_state)
|
|
916
|
-
if worker_id is not None:
|
|
917
|
-
queue.record_worker_heartbeat(worker_id)
|
|
918
|
-
_print_json(console, {"id": message.id, "state": "acked"})
|
|
919
|
-
continue
|
|
920
1040
|
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
)
|
|
1041
|
+
@dataclass(slots=True)
|
|
1042
|
+
class _QueueIterationResult:
|
|
1043
|
+
message: QueueMessage
|
|
1044
|
+
process_result: _ProcessResult
|
|
926
1045
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
1046
|
+
|
|
1047
|
+
def _process_queue_iteration(
|
|
1048
|
+
queue: PersistentQueue,
|
|
1049
|
+
handler: Callable[[Any], Any],
|
|
1050
|
+
*,
|
|
1051
|
+
console: Any,
|
|
1052
|
+
err_console: Any,
|
|
1053
|
+
policy_state: WorkerPolicyState,
|
|
1054
|
+
resolved_policy: PersistentWorkerConfig,
|
|
1055
|
+
retry_store_path: str | None,
|
|
1056
|
+
owned_retry_store: SQLiteAttemptStore | None,
|
|
1057
|
+
max_tries: int,
|
|
1058
|
+
worker_id: str | None,
|
|
1059
|
+
block: bool,
|
|
1060
|
+
timeout: float | None,
|
|
1061
|
+
idle_sleep: float,
|
|
1062
|
+
release_delay: float,
|
|
1063
|
+
dead_letter_on_exhaustion: bool,
|
|
1064
|
+
log_events: bool,
|
|
1065
|
+
mode: str,
|
|
1066
|
+
forever: bool,
|
|
1067
|
+
) -> tuple[_QueueIterationResult | None, SQLiteAttemptStore | None, bool]:
|
|
1068
|
+
if worker_id is not None:
|
|
1069
|
+
queue.record_worker_heartbeat(worker_id)
|
|
1070
|
+
_sleep_for_policy(policy_state, resolved_policy)
|
|
1071
|
+
|
|
1072
|
+
try:
|
|
1073
|
+
message = queue.get_message(
|
|
1074
|
+
block=block,
|
|
1075
|
+
timeout=_poll_timeout(forever, block, timeout),
|
|
1076
|
+
leased_by=worker_id,
|
|
939
1077
|
)
|
|
940
|
-
|
|
941
|
-
queue.record_worker_heartbeat(worker_id)
|
|
1078
|
+
except Empty:
|
|
942
1079
|
if forever:
|
|
943
|
-
|
|
944
|
-
|
|
1080
|
+
if not block:
|
|
1081
|
+
time.sleep(idle_sleep)
|
|
1082
|
+
return None, owned_retry_store, True
|
|
1083
|
+
return None, owned_retry_store, True
|
|
1084
|
+
|
|
1085
|
+
if log_events and err_console is not None:
|
|
1086
|
+
_emit_event(
|
|
1087
|
+
err_console,
|
|
1088
|
+
f"{mode}.lease",
|
|
1089
|
+
queue=message.queue,
|
|
1090
|
+
message_id=message.id,
|
|
1091
|
+
leased_by=message.leased_by,
|
|
1092
|
+
attempts=message.attempts,
|
|
1093
|
+
leased_until=message.leased_until,
|
|
1094
|
+
)
|
|
1095
|
+
|
|
1096
|
+
if retry_store_path is not None and owned_retry_store is None:
|
|
1097
|
+
owned_retry_store = SQLiteAttemptStore(retry_store_path)
|
|
1098
|
+
|
|
1099
|
+
result = _process_message(
|
|
1100
|
+
queue,
|
|
1101
|
+
message,
|
|
1102
|
+
handler,
|
|
1103
|
+
retry_store=owned_retry_store,
|
|
1104
|
+
retry_store_path=None if owned_retry_store is not None else retry_store_path,
|
|
1105
|
+
max_tries=max_tries,
|
|
1106
|
+
release_delay=release_delay,
|
|
1107
|
+
dead_letter_on_exhaustion=dead_letter_on_exhaustion,
|
|
1108
|
+
log_events=log_events,
|
|
1109
|
+
mode=mode,
|
|
1110
|
+
err_console=err_console,
|
|
1111
|
+
)
|
|
1112
|
+
return (
|
|
1113
|
+
_QueueIterationResult(message=message, process_result=result),
|
|
1114
|
+
owned_retry_store,
|
|
1115
|
+
False,
|
|
1116
|
+
)
|
|
945
1117
|
|
|
946
|
-
|
|
1118
|
+
|
|
1119
|
+
def _handle_processed_queue_result(
|
|
1120
|
+
queue: PersistentQueue,
|
|
1121
|
+
message: QueueMessage,
|
|
1122
|
+
result: _ProcessResult,
|
|
1123
|
+
*,
|
|
1124
|
+
console: Any,
|
|
1125
|
+
worker_id: str | None,
|
|
1126
|
+
policy_state: WorkerPolicyState,
|
|
1127
|
+
) -> bool:
|
|
1128
|
+
if not result.processed:
|
|
1129
|
+
return False
|
|
1130
|
+
_record_success(policy_state)
|
|
1131
|
+
if worker_id is not None:
|
|
1132
|
+
queue.record_worker_heartbeat(worker_id)
|
|
1133
|
+
_print_json(console, {"id": message.id, "state": "acked"})
|
|
1134
|
+
return True
|
|
1135
|
+
|
|
1136
|
+
|
|
1137
|
+
def _handle_failed_queue_result(
|
|
1138
|
+
queue: PersistentQueue,
|
|
1139
|
+
message: QueueMessage,
|
|
1140
|
+
result: _ProcessResult,
|
|
1141
|
+
*,
|
|
1142
|
+
console: Any,
|
|
1143
|
+
worker_id: str | None,
|
|
1144
|
+
policy_state: WorkerPolicyState,
|
|
1145
|
+
resolved_policy: PersistentWorkerConfig,
|
|
1146
|
+
forever: bool,
|
|
1147
|
+
) -> bool:
|
|
1148
|
+
_record_failure(
|
|
1149
|
+
policy_state,
|
|
1150
|
+
resolved_policy,
|
|
1151
|
+
permanent=result.permanent_failure,
|
|
1152
|
+
)
|
|
1153
|
+
|
|
1154
|
+
payload = (
|
|
1155
|
+
_message_payload(current_message)
|
|
1156
|
+
if (current_message := queue.inspect(message.id)) is not None
|
|
1157
|
+
else {"id": message.id, "queue": message.queue}
|
|
1158
|
+
)
|
|
1159
|
+
_print_json(
|
|
1160
|
+
console,
|
|
1161
|
+
{
|
|
1162
|
+
**payload,
|
|
1163
|
+
"state": "failed",
|
|
1164
|
+
"last_error": result.last_error,
|
|
1165
|
+
},
|
|
1166
|
+
)
|
|
1167
|
+
if worker_id is not None:
|
|
1168
|
+
queue.record_worker_heartbeat(worker_id)
|
|
1169
|
+
return forever
|
|
947
1170
|
|
|
948
1171
|
|
|
949
1172
|
def _print_dead_letters(
|
|
@@ -1122,13 +1345,13 @@ def _write_config(yaml: Any, config: dict[str, Any], path: Path | None = None) -
|
|
|
1122
1345
|
|
|
1123
1346
|
|
|
1124
1347
|
def _resolve_store_path(explicit: str | None, config: dict[str, Any]) -> str:
|
|
1125
|
-
return str(explicit or config.get("store_path") or
|
|
1348
|
+
return str(explicit or config.get("store_path") or default_queue_store_path())
|
|
1126
1349
|
|
|
1127
1350
|
|
|
1128
1351
|
def _resolve_retry_store_path(explicit: str | None, config: dict[str, Any]) -> str:
|
|
1129
1352
|
value = explicit if explicit is not None else config.get("retry_store_path")
|
|
1130
1353
|
if value is None:
|
|
1131
|
-
return
|
|
1354
|
+
return str(default_retry_store_path())
|
|
1132
1355
|
return str(value)
|
|
1133
1356
|
|
|
1134
1357
|
|
localqueue/failure.py
CHANGED
|
@@ -1,27 +1,19 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Protocol
|
|
4
4
|
|
|
5
5
|
_PERMANENT_FAILURE_TYPES = (
|
|
6
|
-
AssertionError,
|
|
7
|
-
ArithmeticError,
|
|
8
|
-
AttributeError,
|
|
9
|
-
FileNotFoundError,
|
|
10
6
|
ImportError,
|
|
11
|
-
IndexError,
|
|
12
|
-
IsADirectoryError,
|
|
13
|
-
KeyError,
|
|
14
|
-
LookupError,
|
|
15
7
|
ModuleNotFoundError,
|
|
16
8
|
NameError,
|
|
17
|
-
NotADirectoryError,
|
|
18
|
-
TypeError,
|
|
19
|
-
UnicodeError,
|
|
20
|
-
ValueError,
|
|
21
9
|
)
|
|
22
10
|
|
|
23
11
|
|
|
24
|
-
|
|
12
|
+
class _ExitCodeCarrier(Protocol):
|
|
13
|
+
exit_code: int
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def is_permanent_failure(error: BaseException | _ExitCodeCarrier) -> bool:
|
|
25
17
|
if getattr(error, "exit_code", None) == 127:
|
|
26
18
|
return True
|
|
27
19
|
return isinstance(error, _PERMANENT_FAILURE_TYPES)
|
localqueue/paths.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
APP_DIR_NAME = "localqueue"
|
|
7
|
+
DEFAULT_QUEUE_DB_NAME = "queue.sqlite3"
|
|
8
|
+
DEFAULT_RETRY_DB_NAME = "retries.sqlite3"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def data_dir() -> Path:
|
|
12
|
+
data_home = os.environ.get("XDG_DATA_HOME")
|
|
13
|
+
if data_home:
|
|
14
|
+
return Path(data_home) / APP_DIR_NAME
|
|
15
|
+
return Path.home() / ".local" / "share" / APP_DIR_NAME
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def default_queue_store_path() -> Path:
|
|
19
|
+
return data_dir() / DEFAULT_QUEUE_DB_NAME
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def default_retry_store_path() -> Path:
|
|
23
|
+
return data_dir() / DEFAULT_RETRY_DB_NAME
|