pixelarraylib 1.1.6__tar.gz → 1.1.7__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.
Files changed (50) hide show
  1. {pixelarraylib-1.1.6/pixelarraylib.egg-info → pixelarraylib-1.1.7}/PKG-INFO +6 -4
  2. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/__init__.py +1 -1
  3. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/scripts/__init__.py +1 -1
  4. pixelarraylib-1.1.7/pixelarraylib/system/cron_manager.py +535 -0
  5. pixelarraylib-1.1.7/pixelarraylib/utils/name_generator.py +144 -0
  6. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7/pixelarraylib.egg-info}/PKG-INFO +6 -4
  7. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib.egg-info/SOURCES.txt +3 -2
  8. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib.egg-info/requires.txt +6 -4
  9. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pyproject.toml +7 -5
  10. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/requirements.txt +3 -1
  11. pixelarraylib-1.1.6/pixelarraylib/net/request.py +0 -175
  12. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/LICENSE +0 -0
  13. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/MANIFEST.in +0 -0
  14. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/README.md +0 -0
  15. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/__main__.py +0 -0
  16. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/aliyun/__init__.py +0 -0
  17. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/aliyun/acr.py +0 -0
  18. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/aliyun/aliyun_email.py +0 -0
  19. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/aliyun/billing.py +0 -0
  20. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/aliyun/content_scanner.py +0 -0
  21. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/aliyun/domain.py +0 -0
  22. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/aliyun/eci.py +0 -0
  23. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/aliyun/ecs.py +0 -0
  24. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/aliyun/eip.py +0 -0
  25. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/aliyun/fc.py +0 -0
  26. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/aliyun/oss.py +0 -0
  27. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/aliyun/sms.py +0 -0
  28. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/aliyun/sts.py +0 -0
  29. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/db_utils/mysql.py +0 -0
  30. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/db_utils/redis.py +0 -0
  31. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/decorators/__init__.py +0 -0
  32. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/decorators/decorators.py +0 -0
  33. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/gitlab/__init__.py +0 -0
  34. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/gitlab/code_analyzer.py +0 -0
  35. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/gitlab/pypi_package_manager.py +0 -0
  36. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/monitor/__init__.py +0 -0
  37. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/monitor/feishu.py +0 -0
  38. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/scripts/build_website.py +0 -0
  39. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/scripts/collect_code_to_txt.py +0 -0
  40. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/scripts/create_test_case_files.py +0 -0
  41. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/scripts/nginx_proxy_to_ecs.py +0 -0
  42. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/scripts/remove_empty_lines.py +0 -0
  43. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/scripts/tson_convert.py +0 -0
  44. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/system/__init__.py +0 -0
  45. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/system/common.py +0 -0
  46. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib/system/tson.py +0 -0
  47. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib.egg-info/dependency_links.txt +0 -0
  48. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib.egg-info/entry_points.txt +0 -0
  49. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/pixelarraylib.egg-info/top_level.txt +0 -0
  50. {pixelarraylib-1.1.6 → pixelarraylib-1.1.7}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pixelarraylib
3
- Version: 1.1.6
3
+ Version: 1.1.7
4
4
  Summary: PixelArray Python开发工具库 - 包含阿里云服务、数据库工具、装饰器、监控等功能
5
5
  Author-email: Lu qi <qi.lu@pixelarrayai.com>
6
6
  License-Expression: MIT
@@ -29,6 +29,9 @@ Provides-Extra: system
29
29
  Requires-Dist: cryptography; extra == "system"
30
30
  Requires-Dist: paramiko; extra == "system"
31
31
  Requires-Dist: requests; extra == "system"
32
+ Provides-Extra: utils
33
+ Requires-Dist: names; extra == "utils"
34
+ Requires-Dist: Faker; extra == "utils"
32
35
  Provides-Extra: mysql
33
36
  Requires-Dist: pymysql; extra == "mysql"
34
37
  Requires-Dist: aiomysql; extra == "mysql"
@@ -36,9 +39,6 @@ Requires-Dist: requests; extra == "mysql"
36
39
  Provides-Extra: redis
37
40
  Requires-Dist: redis; extra == "redis"
38
41
  Requires-Dist: requests; extra == "redis"
39
- Provides-Extra: net
40
- Requires-Dist: requests; extra == "net"
41
- Requires-Dist: aiohttp; extra == "net"
42
42
  Provides-Extra: gitlab
43
43
  Requires-Dist: requests; extra == "gitlab"
44
44
  Requires-Dist: aiohttp; extra == "gitlab"
@@ -161,6 +161,8 @@ Requires-Dist: requests; extra == "all"
161
161
  Requires-Dist: aiohttp; extra == "all"
162
162
  Requires-Dist: aiofiles; extra == "all"
163
163
  Requires-Dist: pandas; extra == "all"
164
+ Requires-Dist: names; extra == "all"
165
+ Requires-Dist: Faker; extra == "all"
164
166
  Dynamic: license-file
165
167
 
166
168
  # PixelArrayLib - PixelArray Python开发工具库
@@ -20,7 +20,7 @@ PixelArray Python开发工具库
20
20
  from pixelarraylib.gitlab import pypi_package_manager
21
21
  """
22
22
 
23
- __version__ = "1.1.6"
23
+ __version__ = "1.1.7"
24
24
  __author__ = "PixelArray"
25
25
  __email__ = "qi.lu@pixelarrayai.com"
26
26
 
@@ -3,7 +3,7 @@ PixelArrayLib 脚本工具包
3
3
  包含各种实用的命令行工具和脚本
4
4
  """
5
5
 
6
- __version__ = "1.1.6"
6
+ __version__ = "1.1.7"
7
7
  __author__ = "Lu qi"
8
8
 
9
9
  # 导出主要的脚本函数
@@ -0,0 +1,535 @@
1
+ import subprocess
2
+ import tempfile
3
+ import os
4
+ import re
5
+ import traceback
6
+ from typing import List, Dict, Optional
7
+ from pixelarraylib.system.common import execute_command
8
+
9
+
10
+ class CronManager:
11
+ def __init__(self):
12
+ self.cron_file = self._load_crontab()
13
+
14
+ def _load_crontab(self) -> str:
15
+ """
16
+ description:
17
+ 加载crontab文件
18
+ return:
19
+ crontab_file(str): crontab文件内容
20
+ """
21
+ output, success = execute_command(["crontab", "-l"])
22
+ if success:
23
+ return output
24
+ else:
25
+ return ""
26
+
27
+ def _save_crontab(self) -> bool:
28
+ """
29
+ description:
30
+ 保存crontab文件
31
+ return:
32
+ success(bool): 是否成功
33
+ """
34
+ with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
35
+ content = self.cron_file
36
+ if content and not content.endswith("\n"):
37
+ content += "\n"
38
+ f.write(content)
39
+ temp_file = f.name
40
+
41
+ _, success = execute_command(["crontab", temp_file])
42
+ os.remove(temp_file)
43
+ return success
44
+
45
+ def _extract_job_name_from_comment(self, line: str) -> str:
46
+ """
47
+ description:
48
+ 从cron行中提取任务名称(如果有注释)
49
+ parameters:
50
+ line(str): cron行
51
+ return:
52
+ job_name(str): 任务名称
53
+ """
54
+ match = re.search(r"# JOB_NAME:\s*([^\s]+)", line)
55
+ if match:
56
+ return match.group(1)
57
+ return None
58
+
59
+ def _extract_status_from_comment(self, line: str) -> str:
60
+ """
61
+ description:
62
+ 从cron行中提取状态(STATUS:OPEN 或 STATUS:CLOSE)
63
+ parameters:
64
+ line(str): cron行
65
+ return:
66
+ status(str): 状态,OPEN 或 CLOSE,默认为 OPEN
67
+ """
68
+ match = re.search(r"STATUS:\s*(\w+)", line)
69
+ if match:
70
+ return match.group(1).upper()
71
+ return "OPEN"
72
+
73
+ def _generate_job_name_from_command(self, command: str) -> str:
74
+ """
75
+ description:
76
+ 从命令中生成任务名称
77
+ parameters:
78
+ command(str): 任务命令
79
+ return:
80
+ job_name(str): 生成的任务名称
81
+ """
82
+ # 尝试从命令中提取模块名
83
+ if "python -m" in command:
84
+ # 提取模块名
85
+ match = re.search(r"python -m\s+([^\s]+)", command)
86
+ if match:
87
+ module_name = match.group(1)
88
+ # 将模块名转换为任务名
89
+ return module_name.replace(".", "_").replace("/", "_")
90
+
91
+ # 如果无法提取模块名,使用命令的hash值
92
+ import hashlib
93
+
94
+ return f"task_{hashlib.md5(command.encode()).hexdigest()[:8]}"
95
+
96
+ def _validate_schedule(self, schedule: str) -> bool:
97
+ """
98
+ description:
99
+ 验证cron时间格式
100
+ parameters:
101
+ schedule(str): 任务时间安排
102
+ return:
103
+ valid(bool): 是否有效
104
+ """
105
+ parts = schedule.strip().split()
106
+ if len(parts) != 5:
107
+ return False
108
+
109
+ ranges = [
110
+ (0, 59), # 分钟
111
+ (0, 23), # 小时
112
+ (1, 31), # 日期
113
+ (1, 12), # 月份
114
+ (0, 6), # 星期 (0=周日)
115
+ ]
116
+
117
+ try:
118
+ for i, part in enumerate(parts):
119
+ if part == "*":
120
+ continue
121
+
122
+ min_val, max_val = ranges[i]
123
+
124
+ if "," in part:
125
+ for val in part.split(","):
126
+ if "-" in val:
127
+ range_parts = val.split("-")
128
+ if len(range_parts) == 2:
129
+ start = int(range_parts[0].strip())
130
+ end = int(range_parts[1].strip())
131
+ if not (
132
+ min_val <= start <= max_val
133
+ and min_val <= end <= max_val
134
+ and start <= end
135
+ ):
136
+ return False
137
+ else:
138
+ return False
139
+ else:
140
+ val_int = int(val.strip())
141
+ if not (min_val <= val_int <= max_val):
142
+ return False
143
+ elif "-" in part:
144
+ if "/" in part:
145
+ range_part, step_part = part.split("/")
146
+ step = int(step_part.strip())
147
+ if step <= 0:
148
+ return False
149
+
150
+ if "-" in range_part:
151
+ start, end = range_part.split("-")
152
+ start = int(start.strip())
153
+ end = int(end.strip())
154
+ if not (
155
+ min_val <= start <= max_val
156
+ and min_val <= end <= max_val
157
+ and start <= end
158
+ ):
159
+ return False
160
+ else:
161
+ base = int(range_part.strip())
162
+ if not (min_val <= base <= max_val):
163
+ return False
164
+ else:
165
+ start, end = part.split("-")
166
+ start = int(start.strip())
167
+ end = int(end.strip())
168
+ if not (
169
+ min_val <= start <= max_val
170
+ and min_val <= end <= max_val
171
+ and start <= end
172
+ ):
173
+ return False
174
+ elif "/" in part:
175
+ base, step = part.split("/")
176
+ step = int(step.strip())
177
+ if step <= 0:
178
+ return False
179
+
180
+ if base == "*":
181
+ continue
182
+ base_int = int(base.strip())
183
+ if not (min_val <= base_int <= max_val):
184
+ return False
185
+ else:
186
+ val = int(part)
187
+ if not (min_val <= val <= max_val):
188
+ return False
189
+ except (ValueError, IndexError):
190
+ return False
191
+
192
+ return True
193
+
194
+ def list_cron_jobs(self) -> List[Dict[str, str]]:
195
+ """
196
+ description:
197
+ 获取所有cron任务
198
+ return:
199
+ jobs(list[dict]): 所有cron任务
200
+ """
201
+ jobs = []
202
+ if not self.cron_file:
203
+ return jobs
204
+
205
+ lines = self.cron_file.strip().split("\n")
206
+ for line in lines:
207
+ original_line = line
208
+ line = line.strip()
209
+
210
+ # 检查是否是被注释的cron任务行(以#开头但包含JOB_NAME)
211
+ is_commented = line.startswith("#")
212
+ if is_commented:
213
+ # 移除开头的#,继续处理
214
+ line = line[1:].strip()
215
+
216
+ # 检查是否包含JOB_NAME注释(无论是启用还是禁用状态)
217
+ job_name = self._extract_job_name_from_comment(original_line)
218
+ if not job_name:
219
+ # 如果不是我们的任务行(没有JOB_NAME),跳过
220
+ if is_commented or not line or line.startswith("#"):
221
+ continue
222
+ # 对于没有JOB_NAME的普通cron行,尝试从命令生成任务名
223
+ parts = line.split(None, 5)
224
+ if len(parts) >= 6:
225
+ schedule = " ".join(parts[:5])
226
+ command = parts[5]
227
+ job_name = self._generate_job_name_from_command(command)
228
+ status = "OPEN" if not is_commented else "CLOSE"
229
+ jobs.append(
230
+ {
231
+ "job_name": job_name,
232
+ "schedule": schedule,
233
+ "command": command,
234
+ "raw_line": original_line,
235
+ "enabled": not is_commented,
236
+ }
237
+ )
238
+ continue
239
+
240
+ # 提取状态
241
+ status = self._extract_status_from_comment(original_line)
242
+ enabled = not is_commented and status == "OPEN"
243
+
244
+ # 解析cron行
245
+ parts = line.split(None, 5)
246
+ if len(parts) >= 6:
247
+ schedule = " ".join(parts[:5])
248
+ command_with_comment = parts[5]
249
+ command = command_with_comment
250
+
251
+ # 从命令中移除注释部分
252
+ # 移除 JOB_NAME 注释
253
+ if f" # JOB_NAME: {job_name}" in command:
254
+ command = command.replace(f" # JOB_NAME: {job_name}", "")
255
+ # 移除 STATUS 注释
256
+ status_pattern = r"\s*STATUS:\s*\w+"
257
+ command = re.sub(status_pattern, "", command).strip()
258
+
259
+ jobs.append(
260
+ {
261
+ "job_name": job_name,
262
+ "schedule": schedule,
263
+ "command": command,
264
+ "raw_line": original_line,
265
+ "enabled": enabled,
266
+ }
267
+ )
268
+
269
+ return jobs
270
+
271
+ def get_job_by_name(self, job_name: str) -> Optional[Dict[str, str]]:
272
+ """
273
+ description:
274
+ 根据名称获取任务
275
+ parameters:
276
+ job_name(str): 任务名称
277
+ return:
278
+ job(dict): 任务
279
+ """
280
+ jobs = self.list_cron_jobs()
281
+ for job in jobs:
282
+ if job["job_name"] == job_name:
283
+ return job
284
+ return None
285
+
286
+ def add_cron_job(
287
+ self, job_name: str, command: str, schedule: str, enabled: bool = True
288
+ ) -> bool:
289
+ """
290
+ description:
291
+ 添加cron任务
292
+ parameters:
293
+ job_name(str): 任务名称
294
+ command(str): 任务命令
295
+ schedule(str): 任务时间安排
296
+ enabled(bool): 是否启用,默认为True
297
+ return:
298
+ success(bool): 是否成功
299
+ """
300
+ if self.job_exists(job_name):
301
+ raise ValueError(f"任务 '{job_name}' 已存在")
302
+ if not self._validate_schedule(schedule):
303
+ raise ValueError(f"无效的cron时间格式: {schedule}")
304
+
305
+ status = "OPEN" if enabled else "CLOSE"
306
+ new_line = f"{schedule} {command} # JOB_NAME: {job_name} STATUS:{status}"
307
+
308
+ # 如果禁用,在行首添加#
309
+ if not enabled:
310
+ new_line = f"# {new_line}"
311
+
312
+ if self.cron_file:
313
+ self.cron_file += f"\n{new_line}"
314
+ else:
315
+ self.cron_file = new_line
316
+
317
+ success = self._save_crontab()
318
+ return success
319
+
320
+ def remove_cron_job(self, job_name: str):
321
+ """
322
+ description:
323
+ 删除cron任务
324
+ parameters:
325
+ job_name(str): 任务名称
326
+ return:
327
+ success(bool): 是否成功
328
+ """
329
+ if not self.job_exists(job_name):
330
+ raise ValueError(f"任务 '{job_name}' 不存在")
331
+
332
+ lines = self.cron_file.strip().split("\n")
333
+ self.cron_file = "\n".join(
334
+ [
335
+ line
336
+ for line in lines
337
+ if not (line.strip() and f"# JOB_NAME: {job_name}" in line)
338
+ ]
339
+ )
340
+ success = self._save_crontab()
341
+ return success
342
+
343
+ def enable_cron_job(self, job_name: str) -> bool:
344
+ """
345
+ description:
346
+ 启用cron任务(移除行首的#注释)
347
+ parameters:
348
+ job_name(str): 任务名称
349
+ return:
350
+ success(bool): 是否成功
351
+ """
352
+ if not self.job_exists(job_name):
353
+ raise ValueError(f"任务 '{job_name}' 不存在")
354
+
355
+ lines = self.cron_file.strip().split("\n")
356
+ updated_lines = []
357
+
358
+ for line in lines:
359
+ if f"# JOB_NAME: {job_name}" in line:
360
+ # 移除行首的#(如果存在)
361
+ if line.strip().startswith("#"):
362
+ line = line.strip()[1:].strip()
363
+
364
+ # 更新STATUS为OPEN
365
+ line = re.sub(r"STATUS:\s*\w+", "STATUS:OPEN", line)
366
+ updated_lines.append(line)
367
+ else:
368
+ updated_lines.append(line)
369
+
370
+ self.cron_file = "\n".join(updated_lines)
371
+ success = self._save_crontab()
372
+ return success
373
+
374
+ def disable_cron_job(self, job_name: str) -> bool:
375
+ """
376
+ description:
377
+ 禁用cron任务(在行首添加#注释)
378
+ parameters:
379
+ job_name(str): 任务名称
380
+ return:
381
+ success(bool): 是否成功
382
+ """
383
+ if not self.job_exists(job_name):
384
+ raise ValueError(f"任务 '{job_name}' 不存在")
385
+
386
+ lines = self.cron_file.strip().split("\n")
387
+ updated_lines = []
388
+
389
+ for line in lines:
390
+ if f"# JOB_NAME: {job_name}" in line:
391
+ # 如果还没有被注释,添加#
392
+ if not line.strip().startswith("#"):
393
+ line = f"# {line.strip()}"
394
+
395
+ # 更新STATUS为CLOSE
396
+ line = re.sub(r"STATUS:\s*\w+", "STATUS:CLOSE", line)
397
+ # 如果原来没有STATUS,添加STATUS:CLOSE
398
+ if "STATUS:" not in line:
399
+ line = line.rstrip() + " STATUS:CLOSE"
400
+ updated_lines.append(line)
401
+ else:
402
+ updated_lines.append(line)
403
+
404
+ self.cron_file = "\n".join(updated_lines)
405
+ success = self._save_crontab()
406
+ return success
407
+
408
+ def trigger_cron_job(self, job_name: str) -> tuple[str, bool]:
409
+ """
410
+ description:
411
+ 手动触发cron任务
412
+ parameters:
413
+ job_name(str): 任务名称
414
+ return:
415
+ output(str): 命令执行输出
416
+ success(bool): 是否成功
417
+ """
418
+ job = self.get_job_by_name(job_name)
419
+ if not job:
420
+ raise ValueError(f"任务 '{job_name}' 不存在")
421
+
422
+ # 使用shell执行命令,确保环境变量和路径正确
423
+ command = job["command"]
424
+ print(f"执行cron任务命令: {command}")
425
+
426
+ try:
427
+ # 使用subprocess直接执行命令,设置工作目录和环境变量
428
+ # 从命令中提取工作目录,如果命令以cd开头
429
+ working_dir = None
430
+ if command.startswith("cd "):
431
+ # 提取cd后的目录
432
+ parts = command.split("&&", 1)
433
+ if len(parts) > 1:
434
+ cd_part = parts[0].strip()
435
+ if cd_part.startswith("cd "):
436
+ working_dir = cd_part[3:].strip()
437
+ # 更新命令,移除cd部分
438
+ command = parts[1].strip()
439
+
440
+ result = subprocess.run(
441
+ command,
442
+ shell=True,
443
+ capture_output=True,
444
+ text=True,
445
+ cwd=working_dir or os.getcwd(), # 使用提取的工作目录或当前目录
446
+ env={
447
+ **os.environ, # 继承当前环境变量
448
+ "PYTHONPATH": working_dir or os.getcwd(), # 设置Python路径
449
+ },
450
+ timeout=300, # 5分钟超时
451
+ )
452
+
453
+ success = result.returncode == 0
454
+ output = result.stdout + result.stderr if result.stderr else result.stdout
455
+
456
+ print(f"命令执行结果: success={success}, returncode={result.returncode}")
457
+ print(f"输出: {output}")
458
+
459
+ return output, success
460
+
461
+ except subprocess.TimeoutExpired:
462
+ error_msg = f"任务执行超时: {command}"
463
+ print(error_msg)
464
+ return error_msg, False
465
+ except Exception as e:
466
+ error_msg = f"执行任务时发生错误: {str(e)}"
467
+ print(error_msg)
468
+ return error_msg, False
469
+
470
+ def job_exists(self, job_name: str) -> bool:
471
+ """
472
+ description:
473
+ 检查任务是否存在
474
+ parameters:
475
+ job_name(str): 任务名称
476
+ return:
477
+ exists(bool): 是否存在
478
+ """
479
+ return self.get_job_by_name(job_name) is not None
480
+
481
+ def change_cron_job_schedule(self, job_name: str, schedule: str) -> bool:
482
+ """
483
+ description:
484
+ 修改cron任务的时间安排
485
+ parameters:
486
+ job_name(str): 任务名称
487
+ schedule(str): 任务时间安排
488
+ return:
489
+ success(bool): 是否成功
490
+ """
491
+ if not self.job_exists(job_name):
492
+ raise ValueError(f"任务 '{job_name}' 不存在")
493
+
494
+ if not self._validate_schedule(schedule):
495
+ raise ValueError(f"无效的cron时间格式: {schedule}")
496
+
497
+ lines = self.cron_file.strip().split("\n")
498
+ updated_lines = []
499
+
500
+ for line in lines:
501
+ if f"# JOB_NAME: {job_name}" in line:
502
+ # 检查是否被注释
503
+ is_commented = line.strip().startswith("#")
504
+ if is_commented:
505
+ line = line.strip()[1:].strip()
506
+
507
+ parts = line.split(None, 5)
508
+ if len(parts) >= 6:
509
+ command_with_comment = parts[5]
510
+ command = command_with_comment
511
+ # 移除旧的注释
512
+ if f" # JOB_NAME: {job_name}" in command:
513
+ command = command.replace(f" # JOB_NAME: {job_name}", "")
514
+ # 移除旧的STATUS
515
+ command = re.sub(r"\s*STATUS:\s*\w+", "", command).strip()
516
+
517
+ # 提取当前状态
518
+ status = self._extract_status_from_comment(line)
519
+ new_line = (
520
+ f"{schedule} {command} # JOB_NAME: {job_name} STATUS:{status}"
521
+ )
522
+
523
+ # 如果原来是被注释的,保持注释状态
524
+ if is_commented:
525
+ new_line = f"# {new_line}"
526
+
527
+ updated_lines.append(new_line)
528
+ else:
529
+ updated_lines.append(line)
530
+ else:
531
+ updated_lines.append(line)
532
+
533
+ self.cron_file = "\n".join(updated_lines)
534
+ success = self._save_crontab()
535
+ return success
@@ -0,0 +1,144 @@
1
+ import requests
2
+ from typing import Optional, Dict
3
+ import names
4
+
5
+
6
+ class NameGenerator:
7
+ """姓名生成器,支持英文和中文姓名生成,支持多种生成方式"""
8
+
9
+ def __init__(self, use_api: bool = False, language: str = "en"):
10
+ """
11
+ description:
12
+ 初始化姓名生成器,设置生成方式(API或本地库)和语言
13
+ parameters:
14
+ use_api(bool): 是否使用API方式,True表示使用randomuser.me API,False表示使用本地库
15
+ language(str): 语言设置,"en"表示英文,"zh"表示中文,默认为"en"
16
+ return:
17
+
18
+ """
19
+ self.use_api = use_api
20
+ self.api_url = "https://randomuser.me/api/"
21
+ self.language = language
22
+ self._faker_zh = None
23
+
24
+ def _generate_random_name_api(self) -> Optional[Dict[str, str]]:
25
+ """
26
+ description:
27
+ 使用 randomuser.me API 获取随机姓名(支持英文和中文)
28
+ 注意:API不支持中文名,当language="zh"时,如果API返回非中文名则返回None,由调用方降级到本地生成
29
+ parameters:
30
+
31
+ return:
32
+ name(Optional[Dict[str, str]]): 包含first_name、last_name、full_name的字典,如果API调用失败或返回非中文名则返回None
33
+ """
34
+ try:
35
+ # randomuser.me API 不支持中文名生成,当需要中文名时直接返回None,降级到本地生成
36
+ if self.language == "zh":
37
+ return None
38
+
39
+ params = {
40
+ "nat": "us,gb,au,ca", # 限制为英语国家
41
+ "inc": "name", # 只获取姓名信息
42
+ }
43
+ response = requests.get(self.api_url, params=params, timeout=5)
44
+ response.raise_for_status()
45
+ data = response.json()
46
+
47
+ if "results" in data and len(data["results"]) > 0:
48
+ user = data["results"][0]
49
+ first_name = user["name"]["first"]
50
+ last_name = user["name"]["last"]
51
+
52
+ # 英文名格式:名字 姓氏(有空格)
53
+ full_name = f"{first_name} {last_name}"
54
+
55
+ return {
56
+ "first_name": first_name,
57
+ "last_name": last_name,
58
+ "full_name": full_name,
59
+ }
60
+ return None
61
+ except requests.exceptions.RequestException:
62
+ return None
63
+ except (KeyError, IndexError):
64
+ return None
65
+
66
+ def _get_faker_zh(self):
67
+ """
68
+ description:
69
+ 获取或创建中文 Faker 实例(延迟导入)
70
+ return:
71
+ Faker: 中文 Faker 实例
72
+ """
73
+ if self._faker_zh is None:
74
+ from faker import Faker
75
+ self._faker_zh = Faker("zh_CN")
76
+ return self._faker_zh
77
+
78
+ def _generate_random_name_local_chinese(self) -> Dict[str, str]:
79
+ """
80
+ description:
81
+ 使用本地方式生成随机中文姓名(使用 Faker 库)
82
+ parameters:
83
+
84
+ return:
85
+ name(Dict[str, str]): 包含first_name、last_name、full_name的字典
86
+ """
87
+ # 使用 Faker 库生成中文名
88
+ # Faker 的 last_name() 返回姓氏,first_name() 返回名字
89
+ faker = self._get_faker_zh()
90
+ last_name = faker.last_name()
91
+ first_name = faker.first_name()
92
+
93
+ # 中文名格式:姓氏+名字(无空格)
94
+ full_name = f"{last_name}{first_name}"
95
+ return {
96
+ "first_name": first_name,
97
+ "last_name": last_name,
98
+ "full_name": full_name,
99
+ }
100
+
101
+ def _generate_random_name_local_english(self) -> Dict[str, str]:
102
+ """
103
+ description:
104
+ 使用本地方式生成随机英文姓名
105
+ parameters:
106
+
107
+ return:
108
+ name(Dict[str, str]): 包含first_name、last_name、full_name的字典
109
+ """
110
+ first_name = names.get_first_name()
111
+ last_name = names.get_last_name()
112
+ # 英文名格式:名字 姓氏(有空格)
113
+ full_name = f"{first_name} {last_name}"
114
+ return {
115
+ "first_name": first_name,
116
+ "last_name": last_name,
117
+ "full_name": full_name,
118
+ }
119
+
120
+ def _generate_random_name_local(self) -> Dict[str, str]:
121
+ """
122
+ description:
123
+ 使用本地方式生成随机姓名(根据语言设置选择生成方式)
124
+ parameters:
125
+
126
+ return:
127
+ name(Dict[str, str]): 包含first_name、last_name、full_name的字典
128
+ """
129
+ if self.language == "zh":
130
+ return self._generate_random_name_local_chinese()
131
+ else:
132
+ return self._generate_random_name_local_english()
133
+
134
+ def generate_random_name(self) -> Dict[str, str]:
135
+ """
136
+ description:
137
+ 生成随机姓名(支持英文和中文),根据初始化时的设置选择API方式或本地方式,API失败时自动降级到本地方式
138
+ parameters:
139
+
140
+ return:
141
+ name(Dict[str, str]): 包含first_name、last_name、full_name的字典
142
+ """
143
+ name = self._generate_random_name_api() if self.use_api else None
144
+ return name or self._generate_random_name_local()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pixelarraylib
3
- Version: 1.1.6
3
+ Version: 1.1.7
4
4
  Summary: PixelArray Python开发工具库 - 包含阿里云服务、数据库工具、装饰器、监控等功能
5
5
  Author-email: Lu qi <qi.lu@pixelarrayai.com>
6
6
  License-Expression: MIT
@@ -29,6 +29,9 @@ Provides-Extra: system
29
29
  Requires-Dist: cryptography; extra == "system"
30
30
  Requires-Dist: paramiko; extra == "system"
31
31
  Requires-Dist: requests; extra == "system"
32
+ Provides-Extra: utils
33
+ Requires-Dist: names; extra == "utils"
34
+ Requires-Dist: Faker; extra == "utils"
32
35
  Provides-Extra: mysql
33
36
  Requires-Dist: pymysql; extra == "mysql"
34
37
  Requires-Dist: aiomysql; extra == "mysql"
@@ -36,9 +39,6 @@ Requires-Dist: requests; extra == "mysql"
36
39
  Provides-Extra: redis
37
40
  Requires-Dist: redis; extra == "redis"
38
41
  Requires-Dist: requests; extra == "redis"
39
- Provides-Extra: net
40
- Requires-Dist: requests; extra == "net"
41
- Requires-Dist: aiohttp; extra == "net"
42
42
  Provides-Extra: gitlab
43
43
  Requires-Dist: requests; extra == "gitlab"
44
44
  Requires-Dist: aiohttp; extra == "gitlab"
@@ -161,6 +161,8 @@ Requires-Dist: requests; extra == "all"
161
161
  Requires-Dist: aiohttp; extra == "all"
162
162
  Requires-Dist: aiofiles; extra == "all"
163
163
  Requires-Dist: pandas; extra == "all"
164
+ Requires-Dist: names; extra == "all"
165
+ Requires-Dist: Faker; extra == "all"
164
166
  Dynamic: license-file
165
167
 
166
168
  # PixelArrayLib - PixelArray Python开发工具库
@@ -33,7 +33,6 @@ pixelarraylib/gitlab/code_analyzer.py
33
33
  pixelarraylib/gitlab/pypi_package_manager.py
34
34
  pixelarraylib/monitor/__init__.py
35
35
  pixelarraylib/monitor/feishu.py
36
- pixelarraylib/net/request.py
37
36
  pixelarraylib/scripts/__init__.py
38
37
  pixelarraylib/scripts/build_website.py
39
38
  pixelarraylib/scripts/collect_code_to_txt.py
@@ -43,4 +42,6 @@ pixelarraylib/scripts/remove_empty_lines.py
43
42
  pixelarraylib/scripts/tson_convert.py
44
43
  pixelarraylib/system/__init__.py
45
44
  pixelarraylib/system/common.py
46
- pixelarraylib/system/tson.py
45
+ pixelarraylib/system/cron_manager.py
46
+ pixelarraylib/system/tson.py
47
+ pixelarraylib/utils/name_generator.py
@@ -130,6 +130,8 @@ requests
130
130
  aiohttp
131
131
  aiofiles
132
132
  pandas
133
+ names
134
+ Faker
133
135
 
134
136
  [gitlab]
135
137
  requests
@@ -144,10 +146,6 @@ pymysql
144
146
  aiomysql
145
147
  requests
146
148
 
147
- [net]
148
- requests
149
- aiohttp
150
-
151
149
  [redis]
152
150
  redis
153
151
  requests
@@ -156,3 +154,7 @@ requests
156
154
  cryptography
157
155
  paramiko
158
156
  requests
157
+
158
+ [utils]
159
+ names
160
+ Faker
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pixelarraylib"
7
- version = "1.1.6"
7
+ version = "1.1.7"
8
8
  authors = [
9
9
  {name = "Lu qi", email = "qi.lu@pixelarrayai.com"},
10
10
  ]
@@ -41,6 +41,10 @@ system = [
41
41
  "paramiko",
42
42
  "requests",
43
43
  ]
44
+ utils = [
45
+ "names",
46
+ "Faker",
47
+ ]
44
48
  mysql = [
45
49
  "pymysql",
46
50
  "aiomysql",
@@ -50,10 +54,6 @@ redis = [
50
54
  "redis",
51
55
  "requests",
52
56
  ]
53
- net = [
54
- "requests",
55
- "aiohttp",
56
- ]
57
57
  gitlab = [
58
58
  "requests",
59
59
  "aiohttp",
@@ -194,6 +194,8 @@ all = [
194
194
  "aiohttp",
195
195
  "aiofiles",
196
196
  "pandas",
197
+ "names",
198
+ "Faker",
197
199
  ]
198
200
 
199
201
  [project.scripts]
@@ -19,4 +19,6 @@ requests
19
19
  aiohttp
20
20
  cryptography
21
21
  pandas
22
- paramiko
22
+ paramiko
23
+ names
24
+ Faker
@@ -1,175 +0,0 @@
1
- import requests
2
- from typing import Generator
3
-
4
-
5
- class Request:
6
- def __init__(self, trust_env: bool = False):
7
- """
8
- description:
9
- 初始化请求客户端
10
- parameters:
11
- trust_env(bool): 是否信任环境变量中的代理设置,默认为False
12
- """
13
- self.session = requests.Session()
14
- self.session.trust_env = trust_env
15
-
16
- def _post(
17
- self,
18
- url: str,
19
- data: dict = {},
20
- headers: dict = {"Content-Type": "application/json"},
21
- timeout: int = 0,
22
- ):
23
- """
24
- description:
25
- 内部POST请求方法
26
- parameters:
27
- url(str): 请求URL
28
- data(dict): 请求数据
29
- headers(dict): 请求头
30
- timeout(int): 请求超时时间(秒),0表示不设置超时
31
- return:
32
- response(requests.Response): 响应对象
33
- """
34
- kwargs = {
35
- "url": url,
36
- "json": data,
37
- "headers": headers,
38
- }
39
- if timeout:
40
- kwargs["timeout"] = timeout
41
- response = self.session.post(**kwargs)
42
- return response
43
-
44
- def _get(self, url: str, params: dict = {}, headers: dict = {}, timeout: int = 0):
45
- """
46
- description:
47
- 内部GET请求方法
48
- parameters:
49
- url(str): 请求URL
50
- params(dict): 请求参数
51
- headers(dict): 请求头
52
- timeout(int): 请求超时时间(秒),0表示不设置超时
53
- return:
54
- response(requests.Response): 响应对象
55
- """
56
- kwargs = {
57
- "url": url,
58
- "params": params,
59
- "headers": headers,
60
- }
61
- if timeout:
62
- kwargs["timeout"] = timeout
63
- response = self.session.get(**kwargs)
64
- return response
65
-
66
- def post(
67
- self,
68
- url: str,
69
- data: dict = {},
70
- headers: dict = {"Content-Type": "application/json"},
71
- timeout: int = 0,
72
- ) -> tuple[dict, bool]:
73
- """
74
- description:
75
- 发送 POST 请求
76
- parameters:
77
- url(str): 请求 URL
78
- data(dict): 请求数据
79
- headers(dict): 请求头
80
- timeout(int): 请求超时时间
81
- return:
82
- response(dict): 响应数据
83
- flag(bool): 请求是否成功
84
- """
85
- response = self._post(url, data, headers, timeout)
86
-
87
- if response.status_code == 200:
88
- return response.json(), True
89
- else:
90
- print(response.status_code, response.text)
91
- return {
92
- "status_code": response.status_code,
93
- "message": response.text,
94
- }, False
95
-
96
- def get(
97
- self,
98
- url: str,
99
- params: dict = {},
100
- headers: dict = {},
101
- timeout: int = 0,
102
- ) -> tuple[dict, bool]:
103
- """
104
- description:
105
- 发送 GET 请求
106
- parameters:
107
- url(str): 请求 URL
108
- params(dict): 请求参数
109
- headers(dict): 请求头
110
- timeout(int): 请求超时时间
111
- return:
112
- response(dict): 响应数据
113
- flag(bool): 请求是否成功
114
- """
115
- response = self._get(url, params, headers, timeout)
116
-
117
- if response.status_code == 200:
118
- return response.json(), True
119
- else:
120
- print(response.status_code, response.text)
121
- return {
122
- "status_code": response.status_code,
123
- "message": response.text,
124
- }, False
125
-
126
- def post_stream(
127
- self,
128
- url: str,
129
- data: dict = {},
130
- headers: dict = {"Content-Type": "application/json"},
131
- timeout: int = 0,
132
- ) -> Generator[str, None, None]:
133
- """
134
- description:
135
- 发送 POST 流式请求
136
- parameters:
137
- url(str): 请求 URL
138
- data(dict): 请求数据
139
- headers(dict): 请求头
140
- timeout(int): 请求超时时间
141
- return:
142
- response(generator): 响应数据生成器
143
- """
144
- response = self._post(url, data, headers, timeout)
145
- if response.status_code == 200:
146
- return response.iter_lines()
147
-
148
- def get_stream(
149
- self,
150
- url: str,
151
- params: dict = {},
152
- headers: dict = {},
153
- timeout: int = 0,
154
- ) -> Generator[str, None, None]:
155
- """
156
- description:
157
- 发送 GET 流式请求
158
- parameters:
159
- url(str): 请求 URL
160
- params(dict): 请求参数
161
- headers(dict): 请求头
162
- timeout(int): 请求超时时间
163
- return:
164
- response(generator): 响应数据生成器
165
- """
166
- response = self._get(url, params, headers, timeout)
167
- if response.status_code == 200:
168
- return response.iter_lines()
169
-
170
- def __del__(self):
171
- """
172
- description:
173
- 析构函数,关闭会话连接
174
- """
175
- self.session.close()
File without changes
File without changes
File without changes
File without changes