tarsq 0.1.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.
- tarsq-0.1.0/PKG-INFO +13 -0
- tarsq-0.1.0/pyproject.toml +19 -0
- tarsq-0.1.0/tarsq/__init__.py +4 -0
- tarsq-0.1.0/tarsq/client.py +10 -0
- tarsq-0.1.0/tarsq/core/__init__.py +0 -0
- tarsq-0.1.0/tarsq/core/decorator.py +8 -0
- tarsq-0.1.0/tarsq/worker.py +115 -0
tarsq-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tarsq
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A fast, Redis-backed task queue with natural language task routing
|
|
5
|
+
Author: Preqsy
|
|
6
|
+
Author-email: obbyprecious24@gmail.com
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Requires-Dist: redis (>=7.4.0,<8.0.0)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "tarsq"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A fast, Redis-backed task queue with natural language task routing"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "Preqsy",email = "obbyprecious24@gmail.com"}
|
|
7
|
+
]
|
|
8
|
+
requires-python = ">=3.11"
|
|
9
|
+
dependencies = [
|
|
10
|
+
"redis (>=7.4.0,<8.0.0)"
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
[project.scripts]
|
|
15
|
+
tarsq = "tarsq.worker:start"
|
|
16
|
+
|
|
17
|
+
[build-system]
|
|
18
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
19
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import redis
|
|
3
|
+
|
|
4
|
+
r = redis.Redis(host="localhost", port=6379)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def submit(task_name: str, payload=None):
|
|
8
|
+
task = {"task": task_name, "payload": payload or {}, "retries": 0}
|
|
9
|
+
r.lpush("tarsq:queue", json.dumps(task))
|
|
10
|
+
print(f"Submitted: {task_name}")
|
|
File without changes
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import threading
|
|
3
|
+
import time
|
|
4
|
+
import importlib
|
|
5
|
+
|
|
6
|
+
import redis
|
|
7
|
+
|
|
8
|
+
from tarsq.core.decorator import registry
|
|
9
|
+
|
|
10
|
+
r = redis.Redis(host="localhost", port=6379)
|
|
11
|
+
|
|
12
|
+
COLORS = ["\033[94m", "\033[92m", "\033[93m", "\033[95m", "\033[96m"]
|
|
13
|
+
RESET = "\033[0m"
|
|
14
|
+
BOLD = "\033[1m"
|
|
15
|
+
RED = "\033[91m"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def log(worker_id: int, msg: str):
|
|
19
|
+
color = COLORS[worker_id % len(COLORS)]
|
|
20
|
+
print(f"{color}{BOLD}[worker-{worker_id}]{RESET} {msg}")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_task_from_registry(task_name: str):
|
|
24
|
+
if task_name not in registry:
|
|
25
|
+
raise ValueError(f"Unknown task: {task_name}")
|
|
26
|
+
return registry[task_name]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
threads: list[threading.Thread] = []
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def handle_retry(item_dict: dict, retries: int, worker_id: int):
|
|
33
|
+
delay = 2**retries
|
|
34
|
+
task_name = item_dict["task"]
|
|
35
|
+
log(worker_id, f"retrying ↺ {task_name} in {delay}s (attempt {retries}/3)\n")
|
|
36
|
+
time.sleep(delay)
|
|
37
|
+
item_dict["retries"] = retries
|
|
38
|
+
r.lpush("tarsq:queue", json.dumps(item_dict))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def worker(worker_id: int):
|
|
42
|
+
while True:
|
|
43
|
+
key, value = r.brpop("tarsq:queue")
|
|
44
|
+
item_dict = json.loads(value.decode())
|
|
45
|
+
task_name = item_dict["task"]
|
|
46
|
+
task_payload = item_dict["payload"]
|
|
47
|
+
retries = item_dict["retries"]
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
task = get_task_from_registry(task_name)
|
|
51
|
+
log(worker_id, f"picked up → {task_name} {task_payload or ''}")
|
|
52
|
+
try:
|
|
53
|
+
task(**task_payload)
|
|
54
|
+
log(worker_id, f"finished ✓ {task_name}\n")
|
|
55
|
+
except Exception as e:
|
|
56
|
+
retries += 1
|
|
57
|
+
log(
|
|
58
|
+
worker_id,
|
|
59
|
+
f"{RED}failed ✗ {task_name} — {e} (attempt {retries}/3){RESET}",
|
|
60
|
+
)
|
|
61
|
+
if retries < 3:
|
|
62
|
+
threading.Thread(
|
|
63
|
+
target=handle_retry,
|
|
64
|
+
args=(item_dict, retries, worker_id),
|
|
65
|
+
daemon=True,
|
|
66
|
+
).start()
|
|
67
|
+
else:
|
|
68
|
+
log(worker_id, f"giving up ✗ {task_name} — max retries reached\n")
|
|
69
|
+
|
|
70
|
+
except ValueError as e:
|
|
71
|
+
log(worker_id, f"{RED}skipping ✗ {e}{RESET}\n")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def watch():
|
|
75
|
+
while True:
|
|
76
|
+
for i, t in enumerate(threads):
|
|
77
|
+
if not t.is_alive():
|
|
78
|
+
log(i, f"{RED}died — restarting...{RESET}")
|
|
79
|
+
new_thread = threading.Thread(target=worker, args=(i,), daemon=True)
|
|
80
|
+
threads[i] = new_thread
|
|
81
|
+
new_thread.start()
|
|
82
|
+
time.sleep(2)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def start():
|
|
86
|
+
import argparse
|
|
87
|
+
|
|
88
|
+
parser = argparse.ArgumentParser(description="Start tarsq workers")
|
|
89
|
+
parser.add_argument(
|
|
90
|
+
"--workers", type=int, default=5, help="Number of workers (default: 5)"
|
|
91
|
+
)
|
|
92
|
+
parser.add_argument(
|
|
93
|
+
"--app", type=str, help="App module to load tasks from (e.g. 'myapp.tasks')"
|
|
94
|
+
)
|
|
95
|
+
args = parser.parse_args()
|
|
96
|
+
|
|
97
|
+
if args.app:
|
|
98
|
+
importlib.import_module(args.app)
|
|
99
|
+
|
|
100
|
+
num_workers = args.workers
|
|
101
|
+
|
|
102
|
+
print(f"\n{BOLD}Starting {num_workers} workers — Redis @6379{RESET}\n")
|
|
103
|
+
for i in range(num_workers):
|
|
104
|
+
t = threading.Thread(target=worker, args=(i,), daemon=True)
|
|
105
|
+
threads.append(t)
|
|
106
|
+
|
|
107
|
+
for i, t in enumerate(threads):
|
|
108
|
+
log(i, "started")
|
|
109
|
+
t.start()
|
|
110
|
+
print()
|
|
111
|
+
|
|
112
|
+
threading.Thread(target=watch, daemon=True).start()
|
|
113
|
+
|
|
114
|
+
while True:
|
|
115
|
+
time.sleep(1)
|