terryutils 1.0.5__py3-none-any.whl → 1.0.7__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.
- terryutils/__init__.py +2 -1
- terryutils/date_util.py +228 -0
- terryutils/mysql_pool_util.py +63 -0
- terryutils/task_framework.py +222 -0
- {terryutils-1.0.5.dist-info → terryutils-1.0.7.dist-info}/METADATA +1 -1
- terryutils-1.0.7.dist-info/RECORD +10 -0
- terryutils-1.0.5.dist-info/RECORD +0 -7
- {terryutils-1.0.5.dist-info → terryutils-1.0.7.dist-info}/WHEEL +0 -0
- {terryutils-1.0.5.dist-info → terryutils-1.0.7.dist-info}/top_level.txt +0 -0
terryutils/__init__.py
CHANGED
terryutils/date_util.py
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
from datetime import datetime, date, timedelta
|
|
2
|
+
import calendar
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
class DateUtils:
|
|
6
|
+
"""企业级 Python 日期工具类"""
|
|
7
|
+
|
|
8
|
+
COMMON_FORMATS = [
|
|
9
|
+
"%Y-%m-%d %H:%M:%S",
|
|
10
|
+
"%Y-%m-%d %H:%M",
|
|
11
|
+
"%Y-%m-%d",
|
|
12
|
+
"%d/%m/%Y",
|
|
13
|
+
"%d-%m-%Y",
|
|
14
|
+
"%Y/%m/%d",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
# -------------------- 基础获取 --------------------
|
|
18
|
+
@staticmethod
|
|
19
|
+
def now(fmt="%Y-%m-%d %H:%M:%S"):
|
|
20
|
+
"""当前时间"""
|
|
21
|
+
return datetime.now().strftime(fmt)
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def today(fmt="%Y-%m-%d"):
|
|
25
|
+
"""今天"""
|
|
26
|
+
return date.today().strftime(fmt)
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def datetime_now():
|
|
30
|
+
"""返回datetime对象"""
|
|
31
|
+
return datetime.now()
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def date_today():
|
|
35
|
+
"""返回date对象"""
|
|
36
|
+
return date.today()
|
|
37
|
+
|
|
38
|
+
# -------------------- 字符串与datetime互转 --------------------
|
|
39
|
+
@staticmethod
|
|
40
|
+
def parse(date_str, fmt=None):
|
|
41
|
+
"""尝试多种格式解析字符串"""
|
|
42
|
+
if isinstance(date_str, (int, float)):
|
|
43
|
+
return datetime.fromtimestamp(date_str)
|
|
44
|
+
if isinstance(date_str, datetime):
|
|
45
|
+
return date_str
|
|
46
|
+
if fmt:
|
|
47
|
+
return datetime.strptime(date_str, fmt)
|
|
48
|
+
for f in DateUtils.COMMON_FORMATS:
|
|
49
|
+
try:
|
|
50
|
+
return datetime.strptime(date_str, f)
|
|
51
|
+
except:
|
|
52
|
+
continue
|
|
53
|
+
raise ValueError(f"无法解析日期: {date_str}")
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def format(dt, fmt="%Y-%m-%d %H:%M:%S"):
|
|
57
|
+
if isinstance(dt, str):
|
|
58
|
+
dt = DateUtils.parse(dt)
|
|
59
|
+
return dt.strftime(fmt)
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def to_timestamp(dt):
|
|
63
|
+
dt = DateUtils.parse(dt) if isinstance(dt, (str, int, float)) else dt
|
|
64
|
+
return int(dt.timestamp())
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def from_timestamp(ts):
|
|
68
|
+
return datetime.fromtimestamp(ts)
|
|
69
|
+
|
|
70
|
+
# -------------------- 日期加减 --------------------
|
|
71
|
+
@staticmethod
|
|
72
|
+
def add_days(dt, days):
|
|
73
|
+
dt = DateUtils.parse(dt)
|
|
74
|
+
return dt + timedelta(days=days)
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def add_seconds(dt, seconds, ft):
|
|
78
|
+
dt = DateUtils.parse(dt, ft)
|
|
79
|
+
return dt + timedelta(seconds=seconds)
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def add_months(dt, months):
|
|
83
|
+
dt = DateUtils.parse(dt)
|
|
84
|
+
month = dt.month - 1 + months
|
|
85
|
+
year = dt.year + month // 12
|
|
86
|
+
month = month % 12 + 1
|
|
87
|
+
day = min(dt.day, calendar.monthrange(year, month)[1])
|
|
88
|
+
return dt.replace(year=year, month=month, day=day)
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def add_years(dt, years):
|
|
92
|
+
dt = DateUtils.parse(dt)
|
|
93
|
+
try:
|
|
94
|
+
return dt.replace(year=dt.year + years)
|
|
95
|
+
except ValueError:
|
|
96
|
+
# 闰年2月29日处理
|
|
97
|
+
return dt.replace(month=2, day=28, year=dt.year + years)
|
|
98
|
+
|
|
99
|
+
# -------------------- 周/月/季度起止 --------------------
|
|
100
|
+
@staticmethod
|
|
101
|
+
def start_of_day(dt=None):
|
|
102
|
+
dt = DateUtils.parse(dt) if dt else datetime.now()
|
|
103
|
+
return datetime(dt.year, dt.month, dt.day, 0, 0, 0)
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def end_of_day(dt=None):
|
|
107
|
+
dt = DateUtils.parse(dt) if dt else datetime.now()
|
|
108
|
+
return datetime(dt.year, dt.month, dt.day, 23, 59, 59)
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def start_of_week(dt=None):
|
|
112
|
+
dt = DateUtils.parse(dt) if dt else datetime.now()
|
|
113
|
+
start = dt - timedelta(days=dt.weekday())
|
|
114
|
+
return datetime(start.year, start.month, start.day)
|
|
115
|
+
|
|
116
|
+
@staticmethod
|
|
117
|
+
def end_of_week(dt=None):
|
|
118
|
+
dt = DateUtils.parse(dt) if dt else datetime.now()
|
|
119
|
+
end = dt + timedelta(days=6 - dt.weekday())
|
|
120
|
+
return datetime(end.year, end.month, end.day, 23, 59, 59)
|
|
121
|
+
|
|
122
|
+
@staticmethod
|
|
123
|
+
def start_of_month(dt=None):
|
|
124
|
+
dt = DateUtils.parse(dt) if dt else datetime.now()
|
|
125
|
+
return datetime(dt.year, dt.month, 1)
|
|
126
|
+
|
|
127
|
+
@staticmethod
|
|
128
|
+
def end_of_month(dt=None):
|
|
129
|
+
dt = DateUtils.parse(dt) if dt else datetime.now()
|
|
130
|
+
last_day = calendar.monthrange(dt.year, dt.month)[1]
|
|
131
|
+
return datetime(dt.year, dt.month, last_day, 23, 59, 59)
|
|
132
|
+
|
|
133
|
+
@staticmethod
|
|
134
|
+
def start_of_quarter(dt=None):
|
|
135
|
+
dt = DateUtils.parse(dt) if dt else datetime.now()
|
|
136
|
+
month = ((dt.month - 1) // 3) * 3 + 1
|
|
137
|
+
return datetime(dt.year, month, 1)
|
|
138
|
+
|
|
139
|
+
@staticmethod
|
|
140
|
+
def end_of_quarter(dt=None):
|
|
141
|
+
dt = DateUtils.parse(dt) if dt else datetime.now()
|
|
142
|
+
month = ((dt.month - 1) // 3 + 1) * 3
|
|
143
|
+
last_day = calendar.monthrange(dt.year, month)[1]
|
|
144
|
+
return datetime(dt.year, month, last_day, 23, 59, 59)
|
|
145
|
+
|
|
146
|
+
# -------------------- 日期判断 --------------------
|
|
147
|
+
@staticmethod
|
|
148
|
+
def is_leap_year(year):
|
|
149
|
+
return calendar.isleap(year)
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
def weekday(dt=None):
|
|
153
|
+
dt = DateUtils.parse(dt) if dt else datetime.now()
|
|
154
|
+
return dt.weekday() # 0=周一
|
|
155
|
+
|
|
156
|
+
@staticmethod
|
|
157
|
+
def is_weekend(dt=None):
|
|
158
|
+
return DateUtils.weekday(dt) >= 5
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def days_between(start, end):
|
|
162
|
+
start = DateUtils.parse(start)
|
|
163
|
+
end = DateUtils.parse(end)
|
|
164
|
+
return (end - start).days
|
|
165
|
+
|
|
166
|
+
# -------------------- 日期区间 --------------------
|
|
167
|
+
@staticmethod
|
|
168
|
+
def date_range(start, end, step=1):
|
|
169
|
+
start = DateUtils.parse(start)
|
|
170
|
+
end = DateUtils.parse(end)
|
|
171
|
+
days = []
|
|
172
|
+
delta = timedelta(days=step)
|
|
173
|
+
current = start
|
|
174
|
+
while current <= end:
|
|
175
|
+
days.append(current)
|
|
176
|
+
current += delta
|
|
177
|
+
return days
|
|
178
|
+
|
|
179
|
+
# -------------------- 自然语言解析 --------------------
|
|
180
|
+
@staticmethod
|
|
181
|
+
def smart_parse(text):
|
|
182
|
+
"""支持 '今天','明天','昨天','上周一','下个月15号'"""
|
|
183
|
+
today = datetime.now()
|
|
184
|
+
text = text.strip()
|
|
185
|
+
if text == "今天":
|
|
186
|
+
return today
|
|
187
|
+
elif text == "明天":
|
|
188
|
+
return today + timedelta(days=1)
|
|
189
|
+
elif text == "昨天":
|
|
190
|
+
return today - timedelta(days=1)
|
|
191
|
+
m = re.match(r"上周([一二三四五六日])", text)
|
|
192
|
+
if m:
|
|
193
|
+
weekday_map = {"一":0,"二":1,"三":2,"四":3,"五":4,"六":5,"日":6}
|
|
194
|
+
target_weekday = weekday_map[m.group(1)]
|
|
195
|
+
delta_days = today.weekday() - target_weekday + 7
|
|
196
|
+
return today - timedelta(days=delta_days)
|
|
197
|
+
# TODO: 可继续扩展自然语言解析
|
|
198
|
+
return DateUtils.parse(text)
|
|
199
|
+
|
|
200
|
+
@staticmethod
|
|
201
|
+
def add_days_str(date_str, days, fmt="%Y-%m-%d"):
|
|
202
|
+
"""直接对字符串日期加减天数,返回字符串"""
|
|
203
|
+
dt = datetime.strptime(date_str, fmt)
|
|
204
|
+
dt += timedelta(days=days)
|
|
205
|
+
return dt.strftime(fmt)
|
|
206
|
+
|
|
207
|
+
@staticmethod
|
|
208
|
+
def format_day(date_str, fmt="%Y-%m-%d"):
|
|
209
|
+
"""直接对字符串日期加减天数,返回字符串"""
|
|
210
|
+
dt = datetime.strptime(date_str, fmt)
|
|
211
|
+
return dt.strftime("%Y年%m月%d日")
|
|
212
|
+
|
|
213
|
+
@staticmethod
|
|
214
|
+
def to_timestamp(dt):
|
|
215
|
+
dt = DateUtils.parse(dt) if isinstance(dt, (str, int, float)) else dt
|
|
216
|
+
return int(dt.timestamp())
|
|
217
|
+
# ------------------------ 测试 ------------------------
|
|
218
|
+
if __name__ == "__main__":
|
|
219
|
+
print("现在时间:", DateUtils.now())
|
|
220
|
+
print("今天:", DateUtils.today())
|
|
221
|
+
print("月初:", DateUtils.start_of_month())
|
|
222
|
+
print("月末:", DateUtils.end_of_month())
|
|
223
|
+
print("季度开始:", DateUtils.start_of_quarter())
|
|
224
|
+
print("季度结束:", DateUtils.end_of_quarter())
|
|
225
|
+
print("加3天:", DateUtils.add_days(datetime.now(), 3))
|
|
226
|
+
print("是否周末:", DateUtils.is_weekend())
|
|
227
|
+
print("自然语言解析 明天:", DateUtils.smart_parse("明天"))
|
|
228
|
+
print("日期区间:", [d.strftime("%Y-%m-%d") for d in DateUtils.date_range("2025-12-01", "2025-12-05")])
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from dbutils.pooled_db import PooledDB
|
|
2
|
+
import pymysql
|
|
3
|
+
from pymysql.cursors import DictCursor
|
|
4
|
+
|
|
5
|
+
MYSQL_POOL = PooledDB(
|
|
6
|
+
creator=pymysql,
|
|
7
|
+
maxconnections=20, # 最大连接数(重点)
|
|
8
|
+
mincached=2, # 启动时创建
|
|
9
|
+
maxcached=10, # 池中最大空闲
|
|
10
|
+
blocking=True, # 连接耗尽时等待
|
|
11
|
+
host="localhost",
|
|
12
|
+
user="user",
|
|
13
|
+
password="password",
|
|
14
|
+
database="db",
|
|
15
|
+
port=3306,
|
|
16
|
+
charset="utf8mb4",
|
|
17
|
+
cursorclass=DictCursor,
|
|
18
|
+
autocommit=False
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class MysqlClient:
|
|
23
|
+
|
|
24
|
+
def __init__(self, pool):
|
|
25
|
+
self.pool = pool
|
|
26
|
+
|
|
27
|
+
def _execute(self, fn):
|
|
28
|
+
conn = self.pool.connection()
|
|
29
|
+
try:
|
|
30
|
+
result = fn(conn)
|
|
31
|
+
conn.commit()
|
|
32
|
+
return result
|
|
33
|
+
except Exception:
|
|
34
|
+
conn.rollback()
|
|
35
|
+
raise
|
|
36
|
+
finally:
|
|
37
|
+
conn.close() # ❗归还给 pool,不是真 close
|
|
38
|
+
|
|
39
|
+
def execute(self, sql, params=None):
|
|
40
|
+
def run(conn):
|
|
41
|
+
with conn.cursor() as cursor:
|
|
42
|
+
return cursor.execute(sql, params or ())
|
|
43
|
+
return self._execute(run)
|
|
44
|
+
|
|
45
|
+
def executemany(self, sql, params_list):
|
|
46
|
+
def run(conn):
|
|
47
|
+
with conn.cursor() as cursor:
|
|
48
|
+
return cursor.executemany(sql, params_list)
|
|
49
|
+
return self._execute(run)
|
|
50
|
+
|
|
51
|
+
def fetchone(self, sql, params=None):
|
|
52
|
+
def run(conn):
|
|
53
|
+
with conn.cursor() as cursor:
|
|
54
|
+
cursor.execute(sql, params or ())
|
|
55
|
+
return cursor.fetchone()
|
|
56
|
+
return self._execute(run)
|
|
57
|
+
|
|
58
|
+
def fetchall(self, sql, params=None):
|
|
59
|
+
def run(conn):
|
|
60
|
+
with conn.cursor() as cursor:
|
|
61
|
+
cursor.execute(sql, params or ())
|
|
62
|
+
return cursor.fetchall()
|
|
63
|
+
return self._execute(run)
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
import asyncio
|
|
5
|
+
import inspect
|
|
6
|
+
import logging
|
|
7
|
+
import traceback
|
|
8
|
+
import threading
|
|
9
|
+
from queue import Queue, Empty
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import Callable, Optional
|
|
12
|
+
from logging.handlers import RotatingFileHandler
|
|
13
|
+
|
|
14
|
+
log_dir = "logs"
|
|
15
|
+
os.makedirs(log_dir, exist_ok=True)
|
|
16
|
+
|
|
17
|
+
log_file = os.path.join(log_dir, "request_framework.log")
|
|
18
|
+
|
|
19
|
+
formatter = logging.Formatter(
|
|
20
|
+
'%(asctime)s | %(levelname)-5s | %(threadName)s | %(message)s',
|
|
21
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
|
22
|
+
)
|
|
23
|
+
# --- 文件 handler ---
|
|
24
|
+
file_handler = RotatingFileHandler(
|
|
25
|
+
log_file,
|
|
26
|
+
maxBytes=50 * 1024 * 1024,
|
|
27
|
+
backupCount=5,
|
|
28
|
+
encoding='utf-8'
|
|
29
|
+
)
|
|
30
|
+
file_handler.setFormatter(formatter)
|
|
31
|
+
file_handler.setLevel(logging.INFO)
|
|
32
|
+
|
|
33
|
+
# --- 控制台 handler ---
|
|
34
|
+
console_handler = logging.StreamHandler()
|
|
35
|
+
console_handler.setFormatter(formatter)
|
|
36
|
+
console_handler.setLevel(logging.INFO)
|
|
37
|
+
|
|
38
|
+
logger = logging.getLogger("RequestFramework")
|
|
39
|
+
logger.setLevel(logging.INFO)
|
|
40
|
+
|
|
41
|
+
# 防止重复 add
|
|
42
|
+
if not logger.handlers:
|
|
43
|
+
logger.addHandler(file_handler)
|
|
44
|
+
logger.addHandler(console_handler)
|
|
45
|
+
|
|
46
|
+
logger.propagate = False
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# --- 1. 任务实体:统一封装请求 ---
|
|
52
|
+
@dataclass
|
|
53
|
+
class RequestTask:
|
|
54
|
+
name: str # 任务名称(日志用)
|
|
55
|
+
func: Callable # 执行函数 (requests.get 或 sdk.call)
|
|
56
|
+
args: tuple = field(default_factory=tuple)
|
|
57
|
+
kwargs: dict = field(default_factory=dict)
|
|
58
|
+
callback: Optional[Callable] = None # 成功回调
|
|
59
|
+
max_retries: int = 3 # 最大重试次数
|
|
60
|
+
retry_count: int = 0 # 当前重试计数
|
|
61
|
+
priority: int = 10 # 优先级(数字越小越高)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# --- 2. 频率控制器:令牌桶思想 ---
|
|
65
|
+
class RateLimiter:
|
|
66
|
+
def __init__(self, qps: int):
|
|
67
|
+
self.interval = 1.0 / qps
|
|
68
|
+
self.last_run = 0.0
|
|
69
|
+
self._lock = threading.Lock()
|
|
70
|
+
|
|
71
|
+
def wait(self):
|
|
72
|
+
with self._lock:
|
|
73
|
+
elapsed = time.time() - self.last_run
|
|
74
|
+
wait_time = self.interval - elapsed
|
|
75
|
+
if wait_time > 0:
|
|
76
|
+
time.sleep(wait_time)
|
|
77
|
+
self.last_run = time.time()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# --- 3. 调度引擎 ---
|
|
81
|
+
class RequestEngine:
|
|
82
|
+
def __init__(self, worker_num: int = 3, qps: int = 5):
|
|
83
|
+
self.task_queue = Queue() # 待处理队列
|
|
84
|
+
self.error_queue = Queue() # 错误队列
|
|
85
|
+
self.limiter = RateLimiter(qps) # 频率控制
|
|
86
|
+
self.worker_num = worker_num
|
|
87
|
+
self.running = False
|
|
88
|
+
|
|
89
|
+
def add_task(self, task: RequestTask):
|
|
90
|
+
"""外部提交任务接口"""
|
|
91
|
+
self.task_queue.put(task)
|
|
92
|
+
|
|
93
|
+
def start(self):
|
|
94
|
+
"""启动引擎"""
|
|
95
|
+
self.running = True
|
|
96
|
+
for i in range(self.worker_num):
|
|
97
|
+
t = threading.Thread(target=self._worker_loop, name=f"Worker-{i}")
|
|
98
|
+
t.daemon = True
|
|
99
|
+
t.start()
|
|
100
|
+
logger.info(f"引擎启动:工作线程={self.worker_num}, QPS限制={1 / self.limiter.interval}")
|
|
101
|
+
|
|
102
|
+
def _worker_loop(self):
|
|
103
|
+
"""工作线程主循环"""
|
|
104
|
+
while self.running:
|
|
105
|
+
try:
|
|
106
|
+
# 1. 获取任务 (阻塞1秒以便检查running状态)
|
|
107
|
+
task: RequestTask = self.task_queue.get(timeout=1)
|
|
108
|
+
|
|
109
|
+
# 2. 频率控制
|
|
110
|
+
self.limiter.wait()
|
|
111
|
+
|
|
112
|
+
# 3. 执行任务
|
|
113
|
+
self._execute(task)
|
|
114
|
+
|
|
115
|
+
self.task_queue.task_done()
|
|
116
|
+
except Empty:
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
def _execute(self, task: RequestTask):
|
|
120
|
+
"""执行逻辑:包含重试和异常处理"""
|
|
121
|
+
try:
|
|
122
|
+
logger.info(f"正在执行: {task.name}, 参数: {task.args}")
|
|
123
|
+
# 判断并处理异步函数
|
|
124
|
+
if inspect.iscoroutinefunction(task.func):
|
|
125
|
+
# 如果是 async def 定义的函数,启动一个临时的事件循环运行它
|
|
126
|
+
result = asyncio.run(task.func(*task.args, **task.kwargs))
|
|
127
|
+
else:
|
|
128
|
+
# 如果是普通的 def 定义的函数,直接调用
|
|
129
|
+
result = task.func(*task.args, **task.kwargs)
|
|
130
|
+
|
|
131
|
+
# 4. 执行回调
|
|
132
|
+
if task.callback:
|
|
133
|
+
task.callback(result)
|
|
134
|
+
|
|
135
|
+
logger.info(f"执行成功: {task.name}")
|
|
136
|
+
|
|
137
|
+
except Exception as e:
|
|
138
|
+
exc_type, exc_obj, tb = sys.exc_info()
|
|
139
|
+
filename = tb.tb_frame.f_code.co_filename
|
|
140
|
+
lineno = tb.tb_lineno
|
|
141
|
+
traceback.print_exc()
|
|
142
|
+
logger.error(
|
|
143
|
+
f"执行失败: {task.name}, "
|
|
144
|
+
f"{exc_type.__name__} at {filename}:{lineno}, "
|
|
145
|
+
f"错误信息: {e}"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# 5. 重试逻辑
|
|
149
|
+
if task.retry_count < task.max_retries:
|
|
150
|
+
task.retry_count += 1
|
|
151
|
+
wait_s = 2 ** task.retry_count # 指数退避
|
|
152
|
+
logger.warning(f"任务 {task.name} 将在 {wait_s}s 后进行第 {task.retry_count} 次重试")
|
|
153
|
+
|
|
154
|
+
# 异步延迟重试:不阻塞当前线程,放入定时器重新入队
|
|
155
|
+
threading.Timer(wait_s, self.add_task, args=[task]).start()
|
|
156
|
+
else:
|
|
157
|
+
# 6. 最终失败,放入错误队列
|
|
158
|
+
logger.critical(f"任务 {task.name} 达到最大重试次数,放弃处理")
|
|
159
|
+
self.error_queue.put({"task": task, "error": str(e)})
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
if __name__ == "__main__":
|
|
165
|
+
import requests
|
|
166
|
+
|
|
167
|
+
# 1. 初始化引擎(5个并发,每秒最多2个请求)
|
|
168
|
+
engine = RequestEngine(worker_num=3, qps=1)
|
|
169
|
+
# 2. 启动
|
|
170
|
+
engine.start()
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# 示例 A: 封装 requests 请求
|
|
174
|
+
def get_weather(city):
|
|
175
|
+
resp = requests.get(f"https://httpbin.org/get?city={city}", timeout=5)
|
|
176
|
+
resp.raise_for_status()
|
|
177
|
+
return resp.json()
|
|
178
|
+
|
|
179
|
+
def callback_func(result):
|
|
180
|
+
print(result)
|
|
181
|
+
|
|
182
|
+
task1 = RequestTask(
|
|
183
|
+
name="天气查询-北京",
|
|
184
|
+
func=get_weather,
|
|
185
|
+
args=("Beijing",),
|
|
186
|
+
callback=callback_func # 成功后落库
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# 示例 B: 封装 SDK 请求
|
|
191
|
+
class MySDK:
|
|
192
|
+
def upload_file(self, file_id):
|
|
193
|
+
if file_id == "bad_id": raise ValueError("SDK内部错误")
|
|
194
|
+
return {"status": "uploaded", "id": file_id}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
sdk = MySDK()
|
|
198
|
+
task2 = RequestTask(
|
|
199
|
+
name="SDK上传-001",
|
|
200
|
+
func=sdk.upload_file,
|
|
201
|
+
kwargs={"file_id": "file_123"},
|
|
202
|
+
callback=callback_func
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# 示例 C: 故意失败的任务(测试重试)
|
|
206
|
+
task3 = RequestTask(
|
|
207
|
+
name="必失败任务",
|
|
208
|
+
func=sdk.upload_file,
|
|
209
|
+
kwargs={"file_id": "bad_id"},
|
|
210
|
+
max_retries=2
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# 3. 提交任务到队列
|
|
214
|
+
engine.add_task(task1)
|
|
215
|
+
engine.add_task(task2)
|
|
216
|
+
engine.add_task(task3)
|
|
217
|
+
|
|
218
|
+
# 保持主线程运行
|
|
219
|
+
try:
|
|
220
|
+
while True: time.sleep(1)
|
|
221
|
+
except KeyboardInterrupt:
|
|
222
|
+
print("退出中...")
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
terryutils/__init__.py,sha256=rDDyXE01Cmz90yMbiDhWa9AOdsB7JX-KqSmYBYt9PAY,99
|
|
2
|
+
terryutils/date_util.py,sha256=KdsTOd8EjKu7p9gbfBjm0Yd06zokM8WDAD2NfnogPu0,7832
|
|
3
|
+
terryutils/log.py,sha256=ul9DF7-6AwjH-vE7aPB96hFM0OKrYUjjUFmd4xP4678,1063
|
|
4
|
+
terryutils/mysql_pool_util.py,sha256=TI0MxEmdR5CZ4VtSS6lL9CAyzrANLM5-1caUJulYg24,1835
|
|
5
|
+
terryutils/mysql_util.py,sha256=UwnF8z-KNJlMuOnH03iHVPKkpSNj073YSsrvI3x8pkM,3292
|
|
6
|
+
terryutils/task_framework.py,sha256=040gMwDggAo2KFdtnHCNf4u9ctIJh7xCiHqJRTPB1Fo,6927
|
|
7
|
+
terryutils-1.0.7.dist-info/METADATA,sha256=gpy_i3tPNOPFksvc_OzO8ZBIaiN9EmUAQacq5yh4Q6o,344
|
|
8
|
+
terryutils-1.0.7.dist-info/WHEEL,sha256=WnJ8fYhv8N4SYVK2lLYNI6N0kVATA7b0piVUNvqIIJE,91
|
|
9
|
+
terryutils-1.0.7.dist-info/top_level.txt,sha256=LQtzsZsXViaVLHb_rJ4SJiT9ScUfK6dUoy89dP1mLIo,11
|
|
10
|
+
terryutils-1.0.7.dist-info/RECORD,,
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
terryutils/__init__.py,sha256=lxrRwy_RkvG1mcSs6DOv4SLRqZmwHVERKnT8CzgQ6t8,64
|
|
2
|
-
terryutils/log.py,sha256=ul9DF7-6AwjH-vE7aPB96hFM0OKrYUjjUFmd4xP4678,1063
|
|
3
|
-
terryutils/mysql_util.py,sha256=UwnF8z-KNJlMuOnH03iHVPKkpSNj073YSsrvI3x8pkM,3292
|
|
4
|
-
terryutils-1.0.5.dist-info/METADATA,sha256=ZPiFz_6ifTjLHjTs-2TVERdpXkktNn-mRBqk1FRggMo,344
|
|
5
|
-
terryutils-1.0.5.dist-info/WHEEL,sha256=WnJ8fYhv8N4SYVK2lLYNI6N0kVATA7b0piVUNvqIIJE,91
|
|
6
|
-
terryutils-1.0.5.dist-info/top_level.txt,sha256=LQtzsZsXViaVLHb_rJ4SJiT9ScUfK6dUoy89dP1mLIo,11
|
|
7
|
-
terryutils-1.0.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|