dotask 0.2.4__tar.gz → 0.3.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.
- {dotask-0.2.4 → dotask-0.3.0}/PKG-INFO +6 -1
- {dotask-0.2.4 → dotask-0.3.0}/README.md +5 -0
- {dotask-0.2.4 → dotask-0.3.0}/dotask/task.py +9 -9
- dotask-0.3.0/dotask/util/__init__.py +3 -0
- dotask-0.3.0/dotask/util/limit.py +69 -0
- {dotask-0.2.4 → dotask-0.3.0}/dotask/util/shell.py +18 -32
- {dotask-0.2.4 → dotask-0.3.0}/dotask.egg-info/PKG-INFO +6 -1
- {dotask-0.2.4 → dotask-0.3.0}/dotask.egg-info/SOURCES.txt +1 -0
- {dotask-0.2.4 → dotask-0.3.0}/setup.cfg +1 -1
- dotask-0.2.4/dotask/util/__init__.py +0 -2
- {dotask-0.2.4 → dotask-0.3.0}/LICENSE +0 -0
- {dotask-0.2.4 → dotask-0.3.0}/dotask/__init__.py +0 -0
- {dotask-0.2.4 → dotask-0.3.0}/dotask/util/logger.py +0 -0
- {dotask-0.2.4 → dotask-0.3.0}/dotask.egg-info/dependency_links.txt +0 -0
- {dotask-0.2.4 → dotask-0.3.0}/dotask.egg-info/top_level.txt +0 -0
- {dotask-0.2.4 → dotask-0.3.0}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dotask
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: 发布订阅注解方式
|
|
5
5
|
Home-page: https://gitee.com/d-yz/task-manager
|
|
6
6
|
Author: dyz
|
|
@@ -35,6 +35,11 @@ Dynamic: license-file
|
|
|
35
35
|
3. 添加本地调用shell工具函数->可以很好的适配@task消费者
|
|
36
36
|
1. max_concurrent:控制并发执行的shell数量
|
|
37
37
|
2. 引入dotask.Shell 调用Shell(max_concurrent=?).local_shell_execute(cmd,callback)来使用
|
|
38
|
+
### 0.3.x
|
|
39
|
+
1. 添加令牌桶,通过`from dotask.util import TokenBucket`使用,可以更好的限制生产消费速率
|
|
40
|
+
- 参数说明
|
|
41
|
+
- TokenBucket(capacity=1, rate=0),capacity:令牌桶容量,rate:每秒生成速率
|
|
42
|
+
- get_token(block=True),block:决定令牌桶无令牌时是阻塞还是返回false
|
|
38
43
|
## 快速开始
|
|
39
44
|
### 安装
|
|
40
45
|
`pip install dotask`
|
|
@@ -16,6 +16,11 @@
|
|
|
16
16
|
3. 添加本地调用shell工具函数->可以很好的适配@task消费者
|
|
17
17
|
1. max_concurrent:控制并发执行的shell数量
|
|
18
18
|
2. 引入dotask.Shell 调用Shell(max_concurrent=?).local_shell_execute(cmd,callback)来使用
|
|
19
|
+
### 0.3.x
|
|
20
|
+
1. 添加令牌桶,通过`from dotask.util import TokenBucket`使用,可以更好的限制生产消费速率
|
|
21
|
+
- 参数说明
|
|
22
|
+
- TokenBucket(capacity=1, rate=0),capacity:令牌桶容量,rate:每秒生成速率
|
|
23
|
+
- get_token(block=True),block:决定令牌桶无令牌时是阻塞还是返回false
|
|
19
24
|
## 快速开始
|
|
20
25
|
### 安装
|
|
21
26
|
`pip install dotask`
|
|
@@ -33,7 +33,7 @@ class TopicManager:
|
|
|
33
33
|
q = self.get_topic_queue(topic)
|
|
34
34
|
with self.get_topic_lock(topic):
|
|
35
35
|
q.put(data)
|
|
36
|
-
logger.info(f"
|
|
36
|
+
logger.info(f"publish topic-[{topic}]:{data}")
|
|
37
37
|
|
|
38
38
|
def consume(self, subscribe_topics: List[str], handler: Callable) -> None:
|
|
39
39
|
subscribe_set: Set[str] = set(subscribe_topics)
|
|
@@ -51,7 +51,7 @@ class TopicManager:
|
|
|
51
51
|
except queue.Empty:
|
|
52
52
|
continue
|
|
53
53
|
except Exception as e:
|
|
54
|
-
logger.error(f"
|
|
54
|
+
logger.error(f"Topic[{topic}] consumption error:{e}")
|
|
55
55
|
time.sleep(0.01)
|
|
56
56
|
|
|
57
57
|
def stop_all(self):
|
|
@@ -59,7 +59,7 @@ class TopicManager:
|
|
|
59
59
|
with self.global_lock:
|
|
60
60
|
for topic, q in self.topic_queues.items():
|
|
61
61
|
q.join()
|
|
62
|
-
logger.
|
|
62
|
+
logger.warning("\n all task is stopped!")
|
|
63
63
|
|
|
64
64
|
# 初始化全局主题管理器
|
|
65
65
|
topic_manager = TopicManager()
|
|
@@ -102,7 +102,7 @@ def task(
|
|
|
102
102
|
# ------------------------------
|
|
103
103
|
if role == "producer":
|
|
104
104
|
if not topic:
|
|
105
|
-
raise ValueError("
|
|
105
|
+
raise ValueError("The producer must specify the 'topic' parameter (e.g., topic='num')")
|
|
106
106
|
topics = [t.strip() for t in topic.split(",")]
|
|
107
107
|
|
|
108
108
|
def producer_func():
|
|
@@ -111,9 +111,9 @@ def task(
|
|
|
111
111
|
produce_data = func(*fixed_args, **fixed_kwargs)
|
|
112
112
|
for t in topics:
|
|
113
113
|
topic_manager.publish(t, produce_data)
|
|
114
|
-
time.sleep(0.
|
|
114
|
+
time.sleep(0.1)
|
|
115
115
|
except Exception as e:
|
|
116
|
-
|
|
116
|
+
logger.error(f"producer[{thread_name}] error {e}")
|
|
117
117
|
|
|
118
118
|
thread = threading.Thread(
|
|
119
119
|
target=producer_func,
|
|
@@ -128,7 +128,7 @@ def task(
|
|
|
128
128
|
# ------------------------------
|
|
129
129
|
elif role == "consumer":
|
|
130
130
|
if not subscribe:
|
|
131
|
-
raise ValueError("
|
|
131
|
+
raise ValueError("The consumer must specify the 'subscribe' parameter (e.g., subscribe='num')")
|
|
132
132
|
subscribe_topics = [t.strip() for t in subscribe.split(",")]
|
|
133
133
|
# 解析消费后要发布的主题(多个用,分隔)
|
|
134
134
|
publish_topics = [t.strip() for t in publish_after.split(",")] if publish_after else []
|
|
@@ -150,7 +150,7 @@ def task(
|
|
|
150
150
|
# logger.info(f"消费后发布[{t}]:{consume_result}")
|
|
151
151
|
|
|
152
152
|
except Exception as e:
|
|
153
|
-
logger.error(f"
|
|
153
|
+
logger.error(f"Consumer [{thread_name}] failed to process message: {e}")
|
|
154
154
|
|
|
155
155
|
# 开始消费
|
|
156
156
|
topic_manager.consume(subscribe_topics, handler)
|
|
@@ -164,7 +164,7 @@ def task(
|
|
|
164
164
|
return thread
|
|
165
165
|
|
|
166
166
|
else:
|
|
167
|
-
raise ValueError(f"
|
|
167
|
+
raise ValueError(f"Unsupported role: {role}, available options: producer/consumer")
|
|
168
168
|
|
|
169
169
|
wrapper_result = wrapper()
|
|
170
170
|
return wrapper_result
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from threading import Lock
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TokenBucket:
|
|
6
|
+
"""
|
|
7
|
+
令牌桶限流
|
|
8
|
+
- capacity: 令牌桶最大容量
|
|
9
|
+
- rate: 令牌生成速率 (每秒生成多少个令牌)
|
|
10
|
+
"""
|
|
11
|
+
def __init__(self,capacity:int,rate:float):
|
|
12
|
+
self.capacity = max(1,capacity)
|
|
13
|
+
self.rate = max(0.0001,rate)
|
|
14
|
+
# 当前令牌数量
|
|
15
|
+
self.tokens = capacity
|
|
16
|
+
# 上次填充令牌的时间
|
|
17
|
+
self.last_refill_time = time.time()
|
|
18
|
+
self.lock = Lock()
|
|
19
|
+
|
|
20
|
+
def get_token(self,block:bool=False) -> bool:
|
|
21
|
+
"""
|
|
22
|
+
获取令牌
|
|
23
|
+
:param block: 是否阻塞等待令牌(True:阻塞直到获取到令牌,Flase:非阻塞,获取不到直接返回False)
|
|
24
|
+
:return: 是否成功获取令牌
|
|
25
|
+
"""
|
|
26
|
+
with self.lock:
|
|
27
|
+
# 1.填充令牌(根据上次填充时间和速率计算应该补充的令牌数)
|
|
28
|
+
now = time.time()
|
|
29
|
+
time_elapsed = now - self.last_refill_time
|
|
30
|
+
# 新生成令牌数=时间间隔*令牌生成速率
|
|
31
|
+
new_tokens = time_elapsed * self.rate
|
|
32
|
+
# 取最小,防止令牌数超过桶的最大容量(防止超发)
|
|
33
|
+
self.tokens = min(self.capacity,self.tokens+new_tokens)
|
|
34
|
+
self.last_refill_time = now
|
|
35
|
+
|
|
36
|
+
# 2.尝试获取令牌
|
|
37
|
+
if self.tokens >= 1:
|
|
38
|
+
self.tokens -= 1
|
|
39
|
+
return True
|
|
40
|
+
elif block:
|
|
41
|
+
# 计算需要等待的时间
|
|
42
|
+
# 需要等待的时间=(需要的令牌数-当前令牌数)/令牌生成速率
|
|
43
|
+
wait_time = (1-self.tokens)/self.rate
|
|
44
|
+
time.sleep(wait_time)
|
|
45
|
+
# 安全消耗1个令牌,避免令牌数为负
|
|
46
|
+
self.tokens = max(0,self.tokens-1)
|
|
47
|
+
return True
|
|
48
|
+
else:
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
@@ -4,34 +4,20 @@ import subprocess
|
|
|
4
4
|
from typing import Optional,Callable
|
|
5
5
|
|
|
6
6
|
class Shell:
|
|
7
|
-
_shell_semaphore:threading.Semaphore = None
|
|
8
|
-
_semaphore_lock = threading.Lock()
|
|
7
|
+
# _shell_semaphore:threading.Semaphore = None
|
|
8
|
+
# _semaphore_lock = threading.Lock()
|
|
9
9
|
|
|
10
|
-
def __init__(self,max_concurrent:int
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
self._shell_semaphore = threading.Semaphore(max_concurrent)
|
|
16
|
-
self._max_concurrent = max_concurrent
|
|
17
|
-
else:
|
|
18
|
-
with Shell._semaphore_lock:
|
|
19
|
-
if Shell._shell_semaphore is None:
|
|
20
|
-
if max_concurrent <= 0:
|
|
21
|
-
raise ValueError("最大并发数必须大于0")
|
|
22
|
-
Shell._shell_semaphore = threading.Semaphore(max_concurrent)
|
|
23
|
-
self._max_concurrent = max_concurrent
|
|
24
|
-
else:
|
|
25
|
-
if Shell._shell_semaphore._value != max_concurrent:
|
|
26
|
-
logger.warning(f"全局信号量已存在(并发数:{Shell._shell_semaphore._value}),忽略传入的max_concurrent={max_concurrent}")
|
|
27
|
-
self._max_concurrent = Shell._shell_semaphore._value
|
|
10
|
+
def __init__(self,max_concurrent:int=1):
|
|
11
|
+
if max_concurrent <= 0:
|
|
12
|
+
raise ValueError("Maximum concurrency must be greater than 0")
|
|
13
|
+
self._shell_semaphore = threading.Semaphore(max_concurrent)
|
|
14
|
+
self._max_concurrent = max_concurrent
|
|
28
15
|
|
|
29
16
|
def local_shell_execute(self,cmd:str,callback: Optional[Callable[[bool, str], None]] = None) -> bool:
|
|
30
17
|
def execute_and_wait():
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
logger.debug(f"实例:{id(self)}-剩余并发名额: {semaphore._value} | 开始执行命令: {cmd[:50]}")
|
|
18
|
+
with self._shell_semaphore:
|
|
19
|
+
truncated_cmd = cmd[:50] + "..." if len(cmd) > 50 else cmd
|
|
20
|
+
logger.debug(f"Instance {id(self)}: Remaining concurrency quota {self._shell_semaphore._value} | Executing command: {truncated_cmd}")
|
|
35
21
|
proc = None
|
|
36
22
|
try:
|
|
37
23
|
# 创建并执行命令
|
|
@@ -48,15 +34,15 @@ class Shell:
|
|
|
48
34
|
|
|
49
35
|
# 处理结果
|
|
50
36
|
if proc.returncode == 0:
|
|
51
|
-
logger.debug(f"
|
|
37
|
+
logger.debug(f"Command Succeeded: {truncated_cmd}")
|
|
52
38
|
self._callback(callback,success=True, result=stdout.strip())
|
|
53
39
|
else:
|
|
54
|
-
error_msg = f"
|
|
55
|
-
logger.error(f"
|
|
40
|
+
error_msg = f"Code:{proc.returncode},Error_msg:{stderr.strip()}"
|
|
41
|
+
logger.error(f"Command Failed: {truncated_cmd} → {error_msg}")
|
|
56
42
|
self._callback(callback,success=False, result=error_msg)
|
|
57
43
|
|
|
58
44
|
except Exception as ex:
|
|
59
|
-
logger.error(f"
|
|
45
|
+
logger.error(f"Command Failed: {truncated_cmd} → {str(ex)}")
|
|
60
46
|
self._callback(callback,success=False, result=str(ex))
|
|
61
47
|
finally:
|
|
62
48
|
if proc is not None:
|
|
@@ -69,17 +55,17 @@ class Shell:
|
|
|
69
55
|
|
|
70
56
|
try:
|
|
71
57
|
# 启动线程执行命令(守护线程,不阻塞主线程)
|
|
72
|
-
safe_cmd_name = cmd[:20].replace(' ', '_').replace('/', '_').replace('\\', '_').replace('|', '_')
|
|
58
|
+
safe_cmd_name = cmd[:20].replace(' ', '_').replace('/', '_').replace('\\', '_').replace('|', '_')+ "..." if len(cmd) > 20 else cmd
|
|
73
59
|
exec_thread = threading.Thread(
|
|
74
60
|
target=execute_and_wait,
|
|
75
61
|
daemon=True,
|
|
76
62
|
name=f"CmdExecutor-{safe_cmd_name}"
|
|
77
63
|
)
|
|
78
64
|
exec_thread.start()
|
|
79
|
-
logger.debug(f"
|
|
65
|
+
logger.debug(f"Task submitted: {safe_cmd_name}")
|
|
80
66
|
return True
|
|
81
67
|
except Exception as e:
|
|
82
|
-
logger.error(f"
|
|
68
|
+
logger.error(f"Task submission failed: {cmd} → {str(e)}")
|
|
83
69
|
self._callback(callback,success=False,result=str(e))
|
|
84
70
|
return False
|
|
85
71
|
|
|
@@ -89,5 +75,5 @@ class Shell:
|
|
|
89
75
|
try:
|
|
90
76
|
callback(success, result)
|
|
91
77
|
except Exception as ex:
|
|
92
|
-
logger.error(f"
|
|
78
|
+
logger.error(f"Callback function execution failed: {str(ex)}")
|
|
93
79
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dotask
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: 发布订阅注解方式
|
|
5
5
|
Home-page: https://gitee.com/d-yz/task-manager
|
|
6
6
|
Author: dyz
|
|
@@ -35,6 +35,11 @@ Dynamic: license-file
|
|
|
35
35
|
3. 添加本地调用shell工具函数->可以很好的适配@task消费者
|
|
36
36
|
1. max_concurrent:控制并发执行的shell数量
|
|
37
37
|
2. 引入dotask.Shell 调用Shell(max_concurrent=?).local_shell_execute(cmd,callback)来使用
|
|
38
|
+
### 0.3.x
|
|
39
|
+
1. 添加令牌桶,通过`from dotask.util import TokenBucket`使用,可以更好的限制生产消费速率
|
|
40
|
+
- 参数说明
|
|
41
|
+
- TokenBucket(capacity=1, rate=0),capacity:令牌桶容量,rate:每秒生成速率
|
|
42
|
+
- get_token(block=True),block:决定令牌桶无令牌时是阻塞还是返回false
|
|
38
43
|
## 快速开始
|
|
39
44
|
### 安装
|
|
40
45
|
`pip install dotask`
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|