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 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,4 @@
1
+ from tarsq.client import submit
2
+ from tarsq.core.decorator import task
3
+
4
+ __all__ = ["submit", "task"]
@@ -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,8 @@
1
+ registry = {}
2
+
3
+
4
+ def task(description):
5
+ def decorator(func):
6
+ registry[description] = func
7
+ return func
8
+ return decorator
@@ -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)