why-tools 0.2.26__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.
Files changed (51) hide show
  1. venv/bin/activate_this.py +36 -0
  2. venv314/bin/activate_this.py +38 -0
  3. why_tools-0.2.26.dist-info/METADATA +558 -0
  4. why_tools-0.2.26.dist-info/RECORD +51 -0
  5. why_tools-0.2.26.dist-info/WHEEL +5 -0
  6. why_tools-0.2.26.dist-info/top_level.txt +3 -0
  7. ytools/VERSION +1 -0
  8. ytools/__init__.py +35 -0
  9. ytools/alert/__init__.py +11 -0
  10. ytools/alert/wechat.py +132 -0
  11. ytools/arq/__init__.py +11 -0
  12. ytools/arq/client/__init__.py +11 -0
  13. ytools/arq/client/agent.py +160 -0
  14. ytools/arq/client/base.py +129 -0
  15. ytools/arq/client/client.py +99 -0
  16. ytools/arq/setting.py +23 -0
  17. ytools/arq/task/__init__.py +11 -0
  18. ytools/arq/task/task.py +114 -0
  19. ytools/auto_driver/__init__.py +10 -0
  20. ytools/auto_driver/dp/__init__.py +10 -0
  21. ytools/auto_driver/dp/route.py +439 -0
  22. ytools/auto_driver/dp/route_by_fetch.py +403 -0
  23. ytools/auto_driver/dp/rpa.py +270 -0
  24. ytools/auto_driver/rpa_base.py +194 -0
  25. ytools/auto_driver/track.py +356 -0
  26. ytools/error.py +28 -0
  27. ytools/log.py +14 -0
  28. ytools/network/__init__.py +14 -0
  29. ytools/network/header.py +248 -0
  30. ytools/network/request.py +295 -0
  31. ytools/network/response.py +406 -0
  32. ytools/others/__init__.py +11 -0
  33. ytools/others/no_eol.py +213 -0
  34. ytools/utils/__init__.py +10 -0
  35. ytools/utils/cache.py +87 -0
  36. ytools/utils/cache_utils.py +210 -0
  37. ytools/utils/counter.py +76 -0
  38. ytools/utils/date.py +102 -0
  39. ytools/utils/encrypt.py +91 -0
  40. ytools/utils/extractors.py +438 -0
  41. ytools/utils/file.py +24 -0
  42. ytools/utils/host_ip.py +28 -0
  43. ytools/utils/magic.py +609 -0
  44. ytools/utils/magic_for_class.py +134 -0
  45. ytools/utils/nodes.py +158 -0
  46. ytools/utils/package.py +630 -0
  47. ytools/utils/quiter.py +71 -0
  48. ytools/utils/sign.py +20 -0
  49. ytools/utils/track.py +383 -0
  50. ytools/utils/variable.py +668 -0
  51. ytools/version.py +97 -0
ytools/__init__.py ADDED
@@ -0,0 +1,35 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ @File : __init__.py.py
4
+ @Date : 2024/4/15 下午7:28
5
+ @Author : yintian
6
+ @Desc :
7
+ """
8
+
9
+ from ytools.log import logger
10
+ from ytools.version import get_version
11
+ from ytools.utils.magic import empty
12
+
13
+ __version__ = get_version(path=__file__)
14
+
15
+ G = T = C = None
16
+
17
+
18
+ def init_var(name: str):
19
+ global G, T, C
20
+ match name:
21
+ case 'G':
22
+ from ytools.utils.variable import VariableG
23
+ G = VariableG()
24
+ case 'T':
25
+ from ytools.utils.variable import VariableT
26
+ T = VariableT()
27
+ case 'C':
28
+ from ytools.utils.variable import VariableC
29
+ C = VariableC()
30
+ case _:
31
+ print(f"请输入: G/T/C")
32
+
33
+
34
+ if __name__ == '__main__':
35
+ pass
@@ -0,0 +1,11 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ @File : __init__.py.py
4
+ @Author : yintian
5
+ @Date : 2026/1/8 15:00
6
+ @Software: PyCharm
7
+ @Desc :
8
+ """
9
+
10
+ if __name__ == '__main__':
11
+ pass
ytools/alert/wechat.py ADDED
@@ -0,0 +1,132 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ @File : wechat.py
4
+ @Author : yintian
5
+ @Date : 2026/1/8 15:00
6
+ @Software: PyCharm
7
+ @Desc :
8
+ """
9
+ from typing import Literal
10
+
11
+ from ytools import logger
12
+ from ytools.utils.magic import require
13
+
14
+ msg_type_literal = Literal["text", "markdown", "image", "news", "file", "voice", "template_card", ""]
15
+
16
+
17
+ async def async_send_wechat(content: str, to: list, at: list = None, msg_type: msg_type_literal = "text", **kwargs):
18
+ """发送企业微信告警消息
19
+
20
+ Args:
21
+ content: 消息内容(text类型为纯文本,markdown类型为markdown格式)
22
+ to: 机器人webhook URL列表或key列表
23
+ at: at 列表
24
+ msg_type: 消息类型,支持 "text" 和 "markdown",默认为 "text"
25
+ """
26
+
27
+ require("httpx", action="fix")
28
+ import httpx # noqa
29
+ for webhook in to:
30
+ # 如果to是key,需要拼接完整URL,否则直接使用
31
+ if not webhook.startswith("http"):
32
+ # 这里假设webhook是完整URL,如果是key需要根据实际情况拼接
33
+ webhook_url = webhook
34
+ else:
35
+ webhook_url = webhook
36
+
37
+ # 根据消息类型构建payload
38
+ payload = {
39
+ "msgtype": msg_type,
40
+ msg_type: {
41
+ "content": content,
42
+ "mentioned_list": at or [],
43
+ **kwargs
44
+ }
45
+ }
46
+ try:
47
+ async with httpx.AsyncClient(timeout=10.0) as client:
48
+ response = await client.post(webhook_url, json=payload)
49
+ response.raise_for_status()
50
+ result = response.json()
51
+
52
+ if result.get("errcode") == 0:
53
+ logger.info(f"[企业微信]发送成功!-> {content[:50]}...")
54
+ else:
55
+ logger.error(f"[企业微信]发送失败!错误码:{result.get('errcode')},错误信息:{result.get('errmsg')}")
56
+ except Exception as e:
57
+ logger.error(f"[企业微信]发送失败!原因:{e}")
58
+
59
+
60
+ def send_wechat(content: str, to: list, at: list = None, msg_type: msg_type_literal = "text", **kwargs):
61
+ """发送企业微信告警消息
62
+
63
+ Args:
64
+ content: 消息内容(text类型为纯文本,markdown类型为markdown格式)
65
+ to: 机器人webhook URL列表或key列表
66
+ at: at 列表
67
+ msg_type: 消息类型,支持 "text" 和 "markdown",默认为 "text"
68
+ """
69
+
70
+ require("requests", action="fix")
71
+ import requests # noqa
72
+ payload = {
73
+ "msgtype": msg_type,
74
+ msg_type: {
75
+ "content": content,
76
+ "mentioned_list": at or [],
77
+ **kwargs
78
+ }
79
+ }
80
+ for webhook in to:
81
+ # 如果to是key,需要拼接完整URL,否则直接使用
82
+ if not webhook.startswith("http"):
83
+ # 这里假设webhook是完整URL,如果是key需要根据实际情况拼接
84
+ webhook_url = webhook
85
+ else:
86
+ webhook_url = webhook
87
+
88
+ # 根据消息类型构建payload
89
+
90
+ try:
91
+ with requests.Session() as client:
92
+ response = client.post(webhook_url, json=payload, timeout=10)
93
+ response.raise_for_status()
94
+ result = response.json()
95
+
96
+ if result.get("errcode") == 0:
97
+ logger.info(f"[企业微信]发送成功!-> {content[:50]}...")
98
+ else:
99
+ logger.error(f"[企业微信]发送失败!错误码:{result.get('errcode')},错误信息:{result.get('errmsg')}")
100
+ except Exception as e:
101
+ logger.error(f"[企业微信]发送失败!原因:{e}")
102
+
103
+
104
+ def fmt_msg(*rows, msg: str | list = "", title: str = "微信通知", at: list = None, index=True, level: Literal["comment", "warning", "info"] = "info"):
105
+ msg = msg if isinstance(msg, list) else [msg]
106
+ msg = [f"# <font color=\"{level}\">{title}</font>\n", "\n", *msg, "\n"]
107
+
108
+ for row in rows:
109
+ msg.append("\n" +
110
+ "\n".join(
111
+ [f">{f'{_index + 1}. ' if index else ''}{item[0]}: <font color=\"comment\">{item[1]}</font>" for _index, item in
112
+ enumerate(row.items())]
113
+ )
114
+ + "\n")
115
+ if at:
116
+ msg.append(f"\n\n {' '.join([f'<@{_at}>' for _at in at])}")
117
+ content = ''.join(msg)
118
+ return content
119
+
120
+
121
+ def send_msg(*rows, to: list, msg: str | list = "", title: str = "微信通知", at: list = None, index=True, level: Literal["comment", "warning", "info"] = "info"):
122
+ content = fmt_msg(*rows, msg=msg, title=title, at=at, index=index, level=level)
123
+ send_wechat(content=content, to=to, msg_type="markdown")
124
+
125
+
126
+ async def async_send_msg(*rows, to: list, msg: str | list = "", title: str = "微信通知", at: list = None, index=True, level: Literal["comment", "warning", "info"] = "info"):
127
+ content = fmt_msg(*rows, msg=msg, title=title, at=at, index=index, level=level)
128
+ await async_send_wechat(content=content, to=to, msg_type="markdown")
129
+
130
+
131
+ if __name__ == '__main__':
132
+ pass
ytools/arq/__init__.py ADDED
@@ -0,0 +1,11 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ @File : __init__.py.py
4
+ @Author : yintian
5
+ @Date : 2025/10/21 10:07
6
+ @Software: PyCharm
7
+ @Desc :
8
+ """
9
+
10
+ if __name__ == '__main__':
11
+ pass
@@ -0,0 +1,11 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ @File : __init__.py.py
4
+ @Author : yintian
5
+ @Date : 2025/10/21 10:07
6
+ @Software: PyCharm
7
+ @Desc :
8
+ """
9
+
10
+ if __name__ == '__main__':
11
+ pass
@@ -0,0 +1,160 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ @File : agent.py
4
+ @Author : yintian
5
+ @Date : 2025/10/21 11:33
6
+ @Software: PyCharm
7
+ @Desc :
8
+ """
9
+ import asyncio
10
+ import contextlib
11
+ import inspect
12
+ from asyncio import Event
13
+ from typing import Callable, Any
14
+
15
+ from ytools.arq import setting
16
+ from ytools.arq.client.base import BaseClient
17
+ from ytools.arq.task.task import Task
18
+ from ytools.utils import magic
19
+ from ytools.utils.counter import FastWriteCounter
20
+
21
+
22
+ class Agent(BaseClient):
23
+ def __init__(self, worker: Callable[[Task], Any] | None = None, max_concurrency=None, **kwargs):
24
+ super().__init__(**kwargs)
25
+ self.worker = worker or self.run_task
26
+ self.success_tasks = FastWriteCounter()
27
+ self.extra = {
28
+ "success_tasks": self.success_tasks.value,
29
+ }
30
+ if max_concurrency:
31
+ self.context = asyncio.Semaphore(max_concurrency)
32
+ else:
33
+ self.context = contextlib.nullcontext()
34
+
35
+ async def do(self, task):
36
+ async with self.context:
37
+ try:
38
+ res = await self.run_callable(self.worker, task)
39
+ except Exception as e:
40
+ self.log(f"执行任务失败 task_id={task.task_id}: {type(e).__name__}: {e}", level="error")
41
+ res = f"ERROR::{type(e)}|{str(e)}"
42
+ await self.put_result(res, task)
43
+ self.success_tasks.increment()
44
+ self.extra["success_tasks"] = self.success_tasks.value
45
+ if task.callback:
46
+ asyncio.create_task(self.callback(task, res))
47
+
48
+ @staticmethod
49
+ def is_async_callable(func):
50
+ return inspect.iscoroutinefunction(func) or inspect.iscoroutinefunction(getattr(func, "__call__", None))
51
+
52
+ async def run_callable(self, func, *args, **kwargs):
53
+ pre = magic.prepare(func, *args, **kwargs)
54
+ if self.is_async_callable(pre.func):
55
+ return await pre()
56
+ res = await asyncio.to_thread(pre)
57
+ if inspect.isawaitable(res):
58
+ return await res
59
+ return res
60
+
61
+ async def run_task(self, task: Task):
62
+ payload = task.data
63
+ if isinstance(payload, (str, bytes)):
64
+ with contextlib.suppress(Exception):
65
+ payload = task.decode_data()
66
+ self.log(f"收到任务 task_id={task.task_id}: {payload}")
67
+ result = await self.execute_task(task, payload)
68
+ self.log(f"任务结果 task_id={task.task_id}: {result}")
69
+ return result
70
+
71
+ @staticmethod
72
+ def normalize_task_payload(payload):
73
+ if not isinstance(payload, dict):
74
+ return None
75
+ func = payload.get("func")
76
+ if func is None:
77
+ return None
78
+ func = magic.load_object(func)
79
+ args = payload.get("args", ())
80
+ kwargs = payload.get("kwargs", {})
81
+ if not isinstance(args, (list, tuple)):
82
+ args = (args,)
83
+ if kwargs is None:
84
+ kwargs = {}
85
+ if not isinstance(kwargs, dict):
86
+ raise TypeError("task kwargs 必须为 dict")
87
+ return func, tuple(args), kwargs
88
+
89
+ async def execute_task(self, task: Task, payload):
90
+ normalized = self.normalize_task_payload(payload)
91
+ if normalized is None:
92
+ return payload
93
+ func, args, kwargs = normalized
94
+ return await self.run_callable(func, *args, **kwargs, task=task)
95
+
96
+ async def callback(self, task: Task, res):
97
+ try:
98
+ pre = magic.prepare(task.callback, task=task, res=res, result=res)
99
+ if pre.is_async:
100
+ await pre()
101
+ else:
102
+ pre()
103
+
104
+ except Exception as e:
105
+ self.log(f"执行 callback 报错: {e}", level="error")
106
+
107
+ async def run(self, event: Event = None):
108
+ while True:
109
+ try:
110
+ if event and event.is_set():
111
+ await asyncio.sleep(setting.INTERVAL)
112
+ continue
113
+ task: Task = await self.get_task()
114
+ if not task:
115
+ continue
116
+ self.task_count.increment()
117
+ asyncio.create_task(self.do(task))
118
+ finally:
119
+ await asyncio.sleep(setting.INTERVAL)
120
+
121
+ async def get_task(self):
122
+ task_id = await self.zpop(self.tasks_queue)
123
+ if not task_id:
124
+ return None
125
+ data = await self.redis.get(self.get_queue(task_id, base=self.data_queue))
126
+ if data is None:
127
+ self.log(f"task_id:{task_id} 未获取到数据", level="error")
128
+ return None
129
+ return Task(
130
+ data=data,
131
+ client=self,
132
+ task_id=task_id,
133
+ fmt=True
134
+ )
135
+
136
+ async def zpop(self, key: str):
137
+ result = None
138
+ if (await self.redis.info("server")).get("redis_version", "0.0.0") >= "5.0.0":
139
+ result = await self.redis.zpopmin(self.tasks_queue, count=1)
140
+ result = result and result[0]
141
+ else:
142
+ async with self.redis.pipeline(transaction=True) as pipe:
143
+ # 取出最小分数的任务
144
+ await pipe.zrange(key, 0, 0, withscores=False)
145
+ # 删除它
146
+ await pipe.zremrangebyrank(key, 0, 0)
147
+ result, _ = await pipe.execute()
148
+
149
+ if result:
150
+ return result[0].decode() if isinstance(result[0], bytes) else result[0]
151
+ return None
152
+
153
+ async def put_result(self, result, task):
154
+ result_queue = self.get_queue(task.task_id, base=self.result_queue)
155
+ data = task.encode_data(result)
156
+ await self.redis.publish(result_queue, data)
157
+
158
+
159
+ if __name__ == '__main__':
160
+ pass
@@ -0,0 +1,129 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ @File : base.py
4
+ @Author : yintian
5
+ @Date : 2025/10/21 10:07
6
+ @Software: PyCharm
7
+ @Desc :
8
+ """
9
+ import asyncio
10
+ import json
11
+
12
+ from ytools import logger as default_logger
13
+ from ytools.utils.counter import FastWriteCounter
14
+ from ytools.utils.host_ip import get_local_ip
15
+ from ytools.utils.magic import require
16
+
17
+ require("redis")
18
+ from redis.asyncio import Redis # noqa
19
+ from ytools.arq import setting
20
+
21
+
22
+ class BaseClient:
23
+ redis: Redis
24
+ split = ':'
25
+ queue_name: str
26
+ tasks_queue: str
27
+ data_queue: str
28
+ result_queue: str
29
+ status_queue: str
30
+
31
+ def __init__(
32
+ self,
33
+ queue_name=None,
34
+ redis=None,
35
+ logger=None,
36
+ level="info",
37
+ ):
38
+ self.task_count = FastWriteCounter()
39
+ self.extra = {}
40
+ self.logger = logger or default_logger
41
+ self.level = (level or "info").lower()
42
+ self._host_ip = None
43
+ self.set_queue(queue_name)
44
+ if isinstance(redis, dict):
45
+ self.redis = self.make_redis(**redis)
46
+ elif isinstance(redis, str):
47
+ self.redis = Redis.from_url(redis)
48
+ elif redis:
49
+ self.redis = redis
50
+ asyncio.create_task(self.heartbeat())
51
+
52
+ @property
53
+ def info(self):
54
+ return {
55
+ "host_ip": self.get_host_ip(),
56
+ "task_count": self.task_count.value,
57
+ **self.extra
58
+ }
59
+
60
+ def set_queue(self, queue_name):
61
+ self.queue_name = queue_name or setting.DEFAULT_QUEUE_NAME
62
+ self.tasks_queue = self.get_queue("tasks")
63
+ self.data_queue = self.get_queue("data")
64
+ self.result_queue = self.get_queue("result")
65
+ self.status_queue = self.get_queue("status")
66
+
67
+ def get_queue(self, *queue: str, base=None):
68
+ return self.split.join([base or self.queue_name, *queue])
69
+
70
+ def log(self, message, level=None, *args, **kwargs):
71
+ level = (level or self.level or "info").lower()
72
+ logger_func = getattr(self.logger, level, None)
73
+ if callable(logger_func):
74
+ return logger_func(message, *args, **kwargs)
75
+ logger_func = getattr(self.logger, "info", None)
76
+ if callable(logger_func):
77
+ return logger_func(message, *args, **kwargs)
78
+
79
+ def get_host_ip(self):
80
+ if self._host_ip:
81
+ return self._host_ip
82
+ try:
83
+ self._host_ip = get_local_ip()
84
+ except Exception as e:
85
+ self.log(f"获取内网IP失败: {e}", level="error")
86
+ self._host_ip = "unknown"
87
+ return self._host_ip
88
+
89
+ @classmethod
90
+ def make_redis(
91
+ cls,
92
+ host: str,
93
+ port: int,
94
+ db=0,
95
+ password=None,
96
+ **kwargs
97
+ ):
98
+ return Redis(host=host, port=port, db=db, password=password, **kwargs)
99
+
100
+ @classmethod
101
+ def from_redis(cls, queue_name=None, **redis_config):
102
+ return cls(queue_name, redis=redis_config)
103
+
104
+ async def set_status(self, status_id, data=None, expire_time=None):
105
+ status_queue = self.get_queue(status_id, base=self.status_queue)
106
+ expire_time = expire_time or setting.EXPIRE_TIME
107
+ if data is not None:
108
+ await self.redis.set(status_queue, data, ex=expire_time)
109
+ if await self.redis.exists(status_id):
110
+ await self.redis.expire(status_queue, expire_time)
111
+
112
+ async def get_status(self, status_id):
113
+ status_queue = self.get_queue(status_id, base=self.status_queue)
114
+ return await self.redis.get(status_queue)
115
+
116
+ async def del_status(self, status_id):
117
+ status_queue = self.get_queue(status_id, base=self.status_queue)
118
+ return await self.redis.delete(status_queue)
119
+
120
+ async def heartbeat(self):
121
+ interval = 5
122
+ while True:
123
+ h_key = self.get_queue(self.__class__.__name__.lower(), self.get_host_ip(), base=self.queue_name)
124
+ await self.redis.set(h_key, value=json.dumps(self.info), ex=interval + 1)
125
+ await asyncio.sleep(interval)
126
+
127
+
128
+ if __name__ == '__main__':
129
+ pass
@@ -0,0 +1,99 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ @File : Client.py
4
+ @Author : yintian
5
+ @Date : 2025/10/21 10:20
6
+ @Software: PyCharm
7
+ @Desc :
8
+ """
9
+ import asyncio
10
+ import inspect
11
+ from typing import Literal
12
+ from uuid import uuid4
13
+
14
+ from redis.asyncio.client import Pipeline
15
+
16
+ from ytools.arq import setting
17
+ from ytools.arq.client.base import BaseClient
18
+ from ytools.arq.task.task import Task
19
+
20
+
21
+ class Client(BaseClient):
22
+ max_sleep_time = 5
23
+
24
+ def __init__(self, *args, max_task_num: int = None, max_action: Literal["sleep", "break", "raise"] = "sleep", **kwargs):
25
+ self.max_task_num = max_task_num
26
+ self.max_action = max_action
27
+ super().__init__(*args, **kwargs)
28
+
29
+ async def put(self, data, task_id=None, **kwargs):
30
+ task_id = task_id or str(uuid4())
31
+ task = Task(data=data, client=self, task_id=task_id, result_queue=self.get_queue(task_id, base=self.result_queue), **kwargs)
32
+ await self.put_task(task, kwargs.get('auto_ensure', False))
33
+ return task
34
+
35
+ async def put_task(self, task: Task, auto_ensure=False):
36
+ data_queue = self.get_queue(task.task_id, base=self.data_queue)
37
+
38
+ auto_ensure and await task.ensure()
39
+ async with self.redis.pipeline(transaction=True) as pipe:
40
+ if self.max_task_num and await self.check_max(pipe):
41
+ return
42
+ await pipe.zadd(self.tasks_queue, {task.task_id: task.score})
43
+ await pipe.set(data_queue, task.encode_data(), ex=setting.EXPIRE_TIME)
44
+ results = await pipe.execute()
45
+ if not all(results): # 检查是否有命令失败
46
+ raise ValueError(f"投放任务至队列失败: {results}")
47
+ self.task_count.increment()
48
+
49
+ async def check_max(self, pipe: Pipeline):
50
+ while True:
51
+ now_task_num = await pipe.zcard(self.tasks_queue)
52
+ if now_task_num >= self.max_task_num:
53
+ if self.max_action == "sleep":
54
+ self.log(f"当前任务数量: {now_task_num} 超出最大任务数: {self.max_task_num}, 等待任务消费, 休眠 {self.max_sleep_time}s...")
55
+ await asyncio.sleep(self.max_sleep_time)
56
+ elif self.max_action == "break":
57
+ return True
58
+ else:
59
+ raise ValueError("超出最大任务数")
60
+ else:
61
+ return
62
+
63
+ @staticmethod
64
+ async def get_result(task: Task, timeout=None, timeout_back=None):
65
+ res = await task.get_result(timeout=timeout, timeout_back=timeout_back)
66
+ return res
67
+
68
+ async def get_result_by_id(self, task_id, timeout=None, timeout_back=None):
69
+ result_queue = self.get_queue(task_id, base=self.result_queue)
70
+ pubsub = self.redis.pubsub()
71
+ await pubsub.subscribe(result_queue)
72
+
73
+ async def get_result():
74
+ async for msg in pubsub.listen():
75
+ if msg["type"] == "message":
76
+ return msg["data"]
77
+
78
+ try:
79
+ done_future = asyncio.ensure_future(get_result())
80
+ try:
81
+ res = await asyncio.wait_for(done_future, timeout=timeout)
82
+ return res
83
+ except asyncio.TimeoutError:
84
+ if inspect.iscoroutinefunction(timeout_back):
85
+ await timeout_back()
86
+ elif inspect.isawaitable(timeout_back):
87
+ await timeout_back
88
+ elif timeout_back:
89
+ timeout_back()
90
+ raise
91
+ finally:
92
+ done_future.cancel()
93
+ finally:
94
+ await pubsub.unsubscribe(result_queue)
95
+ await pubsub.close()
96
+
97
+
98
+ if __name__ == '__main__':
99
+ pass
ytools/arq/setting.py ADDED
@@ -0,0 +1,23 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ @File : setting.py
4
+ @Author : yintian
5
+ @Date : 2025/10/21 10:30
6
+ @Software: PyCharm
7
+ @Desc :
8
+ """
9
+ # 默认队列名
10
+ DEFAULT_QUEUE_NAME = "ytools:arq"
11
+ # 是否启用加密, 若传入方法则使用传入方法
12
+ ENCRYPT = False
13
+ # 默认编码
14
+ DEFAULT_ENCODING = "utf-8"
15
+ # 轮询休眠
16
+ INTERVAL = 0.01
17
+ # 传入的是否是可序列化数据
18
+ OBJ_DATA = False
19
+ # key 超时删除时间
20
+ EXPIRE_TIME = 300
21
+
22
+ if __name__ == '__main__':
23
+ pass
@@ -0,0 +1,11 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ @File : __init__.py.py
4
+ @Author : yintian
5
+ @Date : 2025/10/21 10:35
6
+ @Software: PyCharm
7
+ @Desc :
8
+ """
9
+
10
+ if __name__ == '__main__':
11
+ pass