pixelarraylib 1.0.0__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.
- arraylib/__init__.py +36 -0
- arraylib/__main__.py +126 -0
- arraylib/aliyun/__init__.py +0 -0
- arraylib/aliyun/aliyun_email.py +130 -0
- arraylib/aliyun/billing.py +477 -0
- arraylib/aliyun/content_scanner.py +253 -0
- arraylib/aliyun/domain.py +434 -0
- arraylib/aliyun/eci.py +47 -0
- arraylib/aliyun/ecs.py +68 -0
- arraylib/aliyun/fc.py +142 -0
- arraylib/aliyun/oss.py +649 -0
- arraylib/aliyun/sms.py +59 -0
- arraylib/aliyun/sts.py +124 -0
- arraylib/db_utils/mysql.py +544 -0
- arraylib/db_utils/redis.py +373 -0
- arraylib/decorators/__init__.py +13 -0
- arraylib/decorators/decorators.py +194 -0
- arraylib/gitlab/__init__.py +0 -0
- arraylib/gitlab/code_analyzer.py +344 -0
- arraylib/gitlab/pypi_package_manager.py +61 -0
- arraylib/monitor/__init__.py +0 -0
- arraylib/monitor/feishu.py +132 -0
- arraylib/net/request.py +143 -0
- arraylib/scripts/__init__.py +22 -0
- arraylib/scripts/collect_code_to_txt.py +327 -0
- arraylib/scripts/create_test_case_files.py +100 -0
- arraylib/scripts/nginx_proxy_to_ecs.py +119 -0
- arraylib/scripts/remove_empty_lines.py +120 -0
- arraylib/scripts/summary_code_count.py +430 -0
- arraylib/system/__init__.py +0 -0
- arraylib/system/common.py +390 -0
- pixelarraylib-1.0.0.dist-info/METADATA +141 -0
- pixelarraylib-1.0.0.dist-info/RECORD +37 -0
- pixelarraylib-1.0.0.dist-info/WHEEL +5 -0
- pixelarraylib-1.0.0.dist-info/entry_points.txt +2 -0
- pixelarraylib-1.0.0.dist-info/licenses/LICENSE +21 -0
- pixelarraylib-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import aiohttp
|
|
2
|
+
import asyncio
|
|
3
|
+
from datetime import datetime, timedelta
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from typing import List, Dict, Optional, Tuple
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GitLabCodeAnalyzer:
|
|
9
|
+
def __init__(self, gitlab_url, private_token):
|
|
10
|
+
self.gitlab_url = gitlab_url.rstrip("/")
|
|
11
|
+
self.headers = {"PRIVATE-TOKEN": private_token}
|
|
12
|
+
self.session = None
|
|
13
|
+
|
|
14
|
+
async def __aenter__(self):
|
|
15
|
+
"""异步上下文管理器入口"""
|
|
16
|
+
self.session = aiohttp.ClientSession(headers=self.headers)
|
|
17
|
+
return self
|
|
18
|
+
|
|
19
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
20
|
+
"""异步上下文管理器出口"""
|
|
21
|
+
if self.session:
|
|
22
|
+
await self.session.close()
|
|
23
|
+
|
|
24
|
+
async def _get_session(self):
|
|
25
|
+
"""获取或创建aiohttp会话"""
|
|
26
|
+
if self.session is None:
|
|
27
|
+
self.session = aiohttp.ClientSession(headers=self.headers)
|
|
28
|
+
return self.session
|
|
29
|
+
|
|
30
|
+
async def _close_session(self):
|
|
31
|
+
"""关闭aiohttp会话"""
|
|
32
|
+
if self.session:
|
|
33
|
+
await self.session.close()
|
|
34
|
+
self.session = None
|
|
35
|
+
|
|
36
|
+
async def get_project_id_name_list(self, group_id=None, owned_only=True):
|
|
37
|
+
"""获取项目的 ID 和名称列表
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
group_id: 组ID,如果指定则只获取该组的项目
|
|
41
|
+
owned_only: 是否只获取用户拥有的项目,默认为True以提高速度
|
|
42
|
+
"""
|
|
43
|
+
projects = await self.get_all_projects(
|
|
44
|
+
group_id, owned_only=owned_only, membership_only=True
|
|
45
|
+
)
|
|
46
|
+
return [(p["id"], p["name"]) for p in projects]
|
|
47
|
+
|
|
48
|
+
async def get_all_projects(
|
|
49
|
+
self, group_id=None, owned_only=True, membership_only=True
|
|
50
|
+
):
|
|
51
|
+
"""获取项目列表
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
group_id: 组ID,如果指定则只获取该组的项目
|
|
55
|
+
owned_only: 是否只获取用户拥有的项目
|
|
56
|
+
membership_only: 是否只获取用户有权限的项目
|
|
57
|
+
"""
|
|
58
|
+
if group_id:
|
|
59
|
+
url = f"{self.gitlab_url}/api/v4/groups/{group_id}/projects"
|
|
60
|
+
else:
|
|
61
|
+
url = f"{self.gitlab_url}/api/v4/projects"
|
|
62
|
+
|
|
63
|
+
# 构建查询参数
|
|
64
|
+
params = {
|
|
65
|
+
"page": 1,
|
|
66
|
+
"per_page": 100,
|
|
67
|
+
"simple": "true", # 只返回基本信息,提高速度
|
|
68
|
+
"order_by": "name",
|
|
69
|
+
"sort": "asc",
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if owned_only:
|
|
73
|
+
params["owned"] = "true"
|
|
74
|
+
if membership_only:
|
|
75
|
+
params["membership"] = "true"
|
|
76
|
+
|
|
77
|
+
projects = []
|
|
78
|
+
page = 1
|
|
79
|
+
session = await self._get_session()
|
|
80
|
+
|
|
81
|
+
while True:
|
|
82
|
+
params["page"] = page
|
|
83
|
+
async with session.get(url, params=params) as response:
|
|
84
|
+
# 检查响应状态
|
|
85
|
+
if response.status != 200:
|
|
86
|
+
print(f"请求失败,状态码: {response.status}")
|
|
87
|
+
text = await response.text()
|
|
88
|
+
print(f"响应内容: {text}")
|
|
89
|
+
break
|
|
90
|
+
|
|
91
|
+
# 获取响应数据
|
|
92
|
+
data = await response.json()
|
|
93
|
+
print(f"第{page}页数据: {len(data)}个项目")
|
|
94
|
+
|
|
95
|
+
# 如果返回的数据为空,说明已经到达最后一页
|
|
96
|
+
if not data:
|
|
97
|
+
break
|
|
98
|
+
|
|
99
|
+
projects.extend(data)
|
|
100
|
+
page += 1
|
|
101
|
+
|
|
102
|
+
# 如果返回的数据少于per_page,说明这是最后一页
|
|
103
|
+
if len(data) < params["per_page"]:
|
|
104
|
+
break
|
|
105
|
+
|
|
106
|
+
print(f"总共获取到 {len(projects)} 个项目")
|
|
107
|
+
return projects
|
|
108
|
+
|
|
109
|
+
async def get_project_code_stats(self, project_id, since=None, until=None):
|
|
110
|
+
"""获取项目的代码统计,包括每个贡献者的提交数和代码行数变化
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
project_id: 项目ID
|
|
114
|
+
since: 开始日期,格式为YYYY-MM-DD
|
|
115
|
+
until: 结束日期,格式为YYYY-MM-DD
|
|
116
|
+
"""
|
|
117
|
+
session = await self._get_session()
|
|
118
|
+
|
|
119
|
+
# 获取贡献者基本信息
|
|
120
|
+
contributors_url = (
|
|
121
|
+
f"{self.gitlab_url}/api/v4/projects/{project_id}/repository/contributors"
|
|
122
|
+
)
|
|
123
|
+
async with session.get(contributors_url) as response:
|
|
124
|
+
if response.status != 200:
|
|
125
|
+
print(f"获取贡献者信息失败,状态码: {response.status}")
|
|
126
|
+
return []
|
|
127
|
+
contributors = await response.json()
|
|
128
|
+
|
|
129
|
+
# 获取所有提交的统计信息(避免重复计算)
|
|
130
|
+
all_commits_stats = await self._get_all_commits_stats(
|
|
131
|
+
session, project_id, since, until
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# 为每个贡献者分配统计信息
|
|
135
|
+
for contributor in contributors:
|
|
136
|
+
author_email = contributor.get("email", "")
|
|
137
|
+
author_name = contributor.get("name", "")
|
|
138
|
+
|
|
139
|
+
# 从所有提交中筛选该贡献者的提交
|
|
140
|
+
contributor_commits = [
|
|
141
|
+
commit
|
|
142
|
+
for commit in all_commits_stats
|
|
143
|
+
if commit.get("author_email", "").lower() == author_email.lower()
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
# 计算该贡献者的统计信息
|
|
147
|
+
# 注意:这里统计的是代码变化量,不是最终代码量
|
|
148
|
+
total_additions = sum(
|
|
149
|
+
commit.get("additions", 0) for commit in contributor_commits
|
|
150
|
+
)
|
|
151
|
+
total_deletions = sum(
|
|
152
|
+
commit.get("deletions", 0) for commit in contributor_commits
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# 更新贡献者统计信息
|
|
156
|
+
contributor["additions"] = total_additions
|
|
157
|
+
contributor["deletions"] = total_deletions
|
|
158
|
+
contributor["total_changes"] = total_additions + total_deletions
|
|
159
|
+
contributor["commit_details"] = contributor_commits
|
|
160
|
+
|
|
161
|
+
# 添加净变化行数(新增 - 删除)
|
|
162
|
+
contributor["net_changes"] = total_additions - total_deletions
|
|
163
|
+
|
|
164
|
+
return contributors
|
|
165
|
+
|
|
166
|
+
async def _get_all_commits_stats(self, session, project_id, since=None, until=None):
|
|
167
|
+
"""获取项目中所有提交的统计信息"""
|
|
168
|
+
commits_url = (
|
|
169
|
+
f"{self.gitlab_url}/api/v4/projects/{project_id}/repository/commits"
|
|
170
|
+
)
|
|
171
|
+
commit_params = {
|
|
172
|
+
"per_page": 100,
|
|
173
|
+
"ref_name": "master", # 只统计master分支的提交
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if since:
|
|
177
|
+
commit_params["since"] = since
|
|
178
|
+
if until:
|
|
179
|
+
commit_params["until"] = until
|
|
180
|
+
|
|
181
|
+
all_commits = []
|
|
182
|
+
page = 1
|
|
183
|
+
|
|
184
|
+
while True:
|
|
185
|
+
commit_params["page"] = page
|
|
186
|
+
async with session.get(commits_url, params=commit_params) as response:
|
|
187
|
+
if response.status != 200:
|
|
188
|
+
print(f"获取提交列表失败,状态码: {response.status}")
|
|
189
|
+
break
|
|
190
|
+
|
|
191
|
+
commits = await response.json()
|
|
192
|
+
if not commits:
|
|
193
|
+
break
|
|
194
|
+
|
|
195
|
+
all_commits.extend(commits)
|
|
196
|
+
page += 1
|
|
197
|
+
|
|
198
|
+
if len(commits) < commit_params["per_page"]:
|
|
199
|
+
break
|
|
200
|
+
|
|
201
|
+
# 过滤掉合并提交,只保留真正的功能提交
|
|
202
|
+
filtered_commits = []
|
|
203
|
+
for commit in all_commits:
|
|
204
|
+
title = commit.get("title", "").lower()
|
|
205
|
+
message = commit.get("message", "").lower()
|
|
206
|
+
|
|
207
|
+
# 过滤掉合并提交
|
|
208
|
+
if (
|
|
209
|
+
title.startswith("merge branch")
|
|
210
|
+
or title.startswith("merge pull request")
|
|
211
|
+
or "merge" in title
|
|
212
|
+
and "into" in title
|
|
213
|
+
):
|
|
214
|
+
continue
|
|
215
|
+
|
|
216
|
+
filtered_commits.append(commit)
|
|
217
|
+
|
|
218
|
+
print(f"总提交数: {len(all_commits)}, 过滤后提交数: {len(filtered_commits)}")
|
|
219
|
+
|
|
220
|
+
# 并发获取每个提交的详细统计
|
|
221
|
+
commit_tasks = []
|
|
222
|
+
for commit in filtered_commits:
|
|
223
|
+
task = self._get_commit_details(session, project_id, commit)
|
|
224
|
+
commit_tasks.append(task)
|
|
225
|
+
|
|
226
|
+
# 等待所有提交详情获取完成
|
|
227
|
+
commit_details_list = await asyncio.gather(*commit_tasks)
|
|
228
|
+
|
|
229
|
+
# 过滤掉None值
|
|
230
|
+
return [commit for commit in commit_details_list if commit is not None]
|
|
231
|
+
|
|
232
|
+
async def _get_commit_details(self, session, project_id, commit):
|
|
233
|
+
"""获取单个提交的详细信息"""
|
|
234
|
+
commit_id = commit["id"]
|
|
235
|
+
commit_stats_url = f"{self.gitlab_url}/api/v4/projects/{project_id}/repository/commits/{commit_id}"
|
|
236
|
+
|
|
237
|
+
async with session.get(commit_stats_url) as commit_response:
|
|
238
|
+
if commit_response.status == 200:
|
|
239
|
+
commit_data = await commit_response.json()
|
|
240
|
+
stats = commit_data.get("stats", {})
|
|
241
|
+
|
|
242
|
+
# 返回详细的提交信息
|
|
243
|
+
return {
|
|
244
|
+
"id": commit_id,
|
|
245
|
+
"short_id": commit.get("short_id", ""),
|
|
246
|
+
"title": commit.get("title", ""),
|
|
247
|
+
"message": commit.get("message", ""),
|
|
248
|
+
"author_name": commit.get("author_name", ""),
|
|
249
|
+
"author_email": commit.get("author_email", ""),
|
|
250
|
+
"committed_date": commit.get("committed_date", ""),
|
|
251
|
+
"created_at": commit.get("created_at", ""),
|
|
252
|
+
"additions": stats.get("additions", 0),
|
|
253
|
+
"deletions": stats.get("deletions", 0),
|
|
254
|
+
"total": stats.get("total", 0),
|
|
255
|
+
}
|
|
256
|
+
else:
|
|
257
|
+
return None
|
|
258
|
+
|
|
259
|
+
async def generate_code_report(self, group_id=None, since=None, until=None):
|
|
260
|
+
"""生成代码统计报告
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
group_id: 组ID,如果指定则只获取该组的项目
|
|
264
|
+
since: 开始日期,格式为YYYY-MM-DD
|
|
265
|
+
until: 结束日期,格式为YYYY-MM-DD
|
|
266
|
+
"""
|
|
267
|
+
projects = await self.get_all_projects(group_id)
|
|
268
|
+
report_data = []
|
|
269
|
+
|
|
270
|
+
# 并发处理所有项目
|
|
271
|
+
project_tasks = []
|
|
272
|
+
for project in projects:
|
|
273
|
+
task = self._process_project_for_report(project, since, until)
|
|
274
|
+
project_tasks.append(task)
|
|
275
|
+
|
|
276
|
+
# 等待所有项目处理完成
|
|
277
|
+
project_results = await asyncio.gather(*project_tasks)
|
|
278
|
+
|
|
279
|
+
# 汇总所有项目的数据
|
|
280
|
+
for project_data in project_results:
|
|
281
|
+
report_data.extend(project_data)
|
|
282
|
+
|
|
283
|
+
if not report_data:
|
|
284
|
+
print("没有找到任何贡献者数据")
|
|
285
|
+
return pd.DataFrame()
|
|
286
|
+
|
|
287
|
+
return report_data
|
|
288
|
+
|
|
289
|
+
async def _process_project_for_report(self, project, since=None, until=None):
|
|
290
|
+
"""处理单个项目的报告数据"""
|
|
291
|
+
project_id = project["id"]
|
|
292
|
+
project_name = project["name"]
|
|
293
|
+
project_data = []
|
|
294
|
+
|
|
295
|
+
print(f"分析项目: {project_name}")
|
|
296
|
+
|
|
297
|
+
# 获取贡献者统计,包含代码行数信息
|
|
298
|
+
contributors = await self.get_project_code_stats(
|
|
299
|
+
project_id, since=since, until=until
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
for contributor in contributors:
|
|
303
|
+
project_data.append(
|
|
304
|
+
{
|
|
305
|
+
"project": project_name,
|
|
306
|
+
"author": contributor["name"],
|
|
307
|
+
"email": contributor["email"],
|
|
308
|
+
"commits": contributor["commits"],
|
|
309
|
+
"additions": contributor.get("additions", 0),
|
|
310
|
+
"deletions": contributor.get("deletions", 0),
|
|
311
|
+
"total_changes": contributor.get("total_changes", 0),
|
|
312
|
+
"net_changes": contributor.get("net_changes", 0),
|
|
313
|
+
}
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
return project_data
|
|
317
|
+
|
|
318
|
+
async def get_commit_details_report(self, project_id, since=None, until=None):
|
|
319
|
+
"""获取详细的提交信息报告
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
project_id: 项目ID
|
|
323
|
+
since: 开始日期,格式为YYYY-MM-DD
|
|
324
|
+
until: 结束日期,格式为YYYY-MM-DD
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
List[Dict]: 包含所有提交详细信息的列表
|
|
328
|
+
"""
|
|
329
|
+
contributors = await self.get_project_code_stats(
|
|
330
|
+
project_id, since=since, until=until
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
all_commits = []
|
|
334
|
+
for contributor in contributors:
|
|
335
|
+
commit_details = contributor.get("commit_details", [])
|
|
336
|
+
for commit in commit_details:
|
|
337
|
+
commit["contributor_name"] = contributor.get("name", "")
|
|
338
|
+
commit["contributor_email"] = contributor.get("email", "")
|
|
339
|
+
all_commits.append(commit)
|
|
340
|
+
|
|
341
|
+
# 按提交日期排序
|
|
342
|
+
all_commits.sort(key=lambda x: x.get("committed_date", ""), reverse=True)
|
|
343
|
+
|
|
344
|
+
return all_commits
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class GitLabPyPIPackageManager:
|
|
6
|
+
def __init__(self, project_id: str, token: str, api_version: str = "v4"):
|
|
7
|
+
self.gitlab_url = "https://gitlab.com"
|
|
8
|
+
self.project_id = project_id
|
|
9
|
+
self.token = token
|
|
10
|
+
self.api_version = api_version
|
|
11
|
+
self.base_url = f"{self.gitlab_url}/api/{api_version}/projects/{project_id}"
|
|
12
|
+
self.headers = {
|
|
13
|
+
"Authorization": f"Bearer {token}",
|
|
14
|
+
"Content-Type": "application/json",
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
def _find_package_id(self, package_name: str, package_version: str) -> str:
|
|
18
|
+
url = f"{self.base_url}/packages"
|
|
19
|
+
params = {
|
|
20
|
+
"package_type": "pypi",
|
|
21
|
+
"package_name": package_name,
|
|
22
|
+
"package_version": package_version,
|
|
23
|
+
}
|
|
24
|
+
response = requests.get(url, headers=self.headers, params=params)
|
|
25
|
+
if response.status_code == 200:
|
|
26
|
+
for package in response.json():
|
|
27
|
+
if (
|
|
28
|
+
package["name"] == package_name
|
|
29
|
+
and package["version"] == package_version
|
|
30
|
+
):
|
|
31
|
+
return package["id"]
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
def list_packages(self) -> List[str]:
|
|
35
|
+
url = f"{self.base_url}/packages"
|
|
36
|
+
params = {"package_type": "pypi"}
|
|
37
|
+
response = requests.get(url, headers=self.headers, params=params)
|
|
38
|
+
if response.status_code == 200:
|
|
39
|
+
return response.json()
|
|
40
|
+
else:
|
|
41
|
+
return []
|
|
42
|
+
|
|
43
|
+
def list_package_versions(self, package_name: str) -> List[str]:
|
|
44
|
+
url = f"{self.base_url}/packages"
|
|
45
|
+
params = {"package_type": "pypi", "package_name": package_name}
|
|
46
|
+
response = requests.get(url, headers=self.headers, params=params)
|
|
47
|
+
if response.status_code == 200:
|
|
48
|
+
return [version["version"] for version in response.json()]
|
|
49
|
+
else:
|
|
50
|
+
return []
|
|
51
|
+
|
|
52
|
+
def delete_package(self, package_name: str, package_version: str):
|
|
53
|
+
package_id = self._find_package_id(package_name, package_version)
|
|
54
|
+
if package_id is None:
|
|
55
|
+
raise ValueError(f"Package {package_name} {package_version} not found")
|
|
56
|
+
url = f"{self.base_url}/packages/{package_id}"
|
|
57
|
+
response = requests.delete(url, headers=self.headers)
|
|
58
|
+
if response.status_code == 200:
|
|
59
|
+
return True
|
|
60
|
+
else:
|
|
61
|
+
return False
|
|
File without changes
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import json
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Feishu:
|
|
7
|
+
channel_map = {
|
|
8
|
+
"矩阵像素订阅群": "https://open.feishu.cn/open-apis/bot/v2/hook/6e368741-ab2e-46f4-a945-5c1182303f91",
|
|
9
|
+
"devtoolkit服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/b9e7cfa1-c63f-4a9f-9699-286f7316784b",
|
|
10
|
+
"baymax服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/5d8d4fa6-67c4-4202-9122-389d1ec2b668",
|
|
11
|
+
"videodriver服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/5359b92d-02ab-47ca-a617-58b8152eaa2d",
|
|
12
|
+
"arraycut服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/e610b1e8-f867-4670-8d4d-0553f64884f0",
|
|
13
|
+
"knowledgebase微服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/5f007914-235a-4287-8349-6c1dd7c70024",
|
|
14
|
+
"llm微服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/54942aa6-24f1-4851-8fe9-d7c87572d00a",
|
|
15
|
+
"thirdparty微服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/b1e6237a-1323-4ad9-96f4-d74de5cdc00f",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
def __init__(self, channel_name):
|
|
19
|
+
self.webhook_url = self.channel_map[channel_name]
|
|
20
|
+
|
|
21
|
+
def send(self, text):
|
|
22
|
+
print(text)
|
|
23
|
+
headers = {"Content-Type": "application/json"}
|
|
24
|
+
data = {"msg_type": "text", "content": {"text": text}}
|
|
25
|
+
response = requests.post(
|
|
26
|
+
self.webhook_url, headers=headers, data=json.dumps(data)
|
|
27
|
+
)
|
|
28
|
+
return bool(
|
|
29
|
+
response
|
|
30
|
+
and response.json().get("StatusCode") == 0
|
|
31
|
+
and response.json().get("StatusMessage") == "success"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
async def send_async(self, text: str):
|
|
35
|
+
return await asyncio.to_thread(self.send, text)
|
|
36
|
+
|
|
37
|
+
def send_markdown(
|
|
38
|
+
self,
|
|
39
|
+
markdown_content: str,
|
|
40
|
+
title: str,
|
|
41
|
+
template: str = "turquoise",
|
|
42
|
+
):
|
|
43
|
+
headers = {"Content-Type": "application/json; charset=utf-8"}
|
|
44
|
+
card = {
|
|
45
|
+
"config": {"wide_screen_mode": True, "enable_forward": True},
|
|
46
|
+
"header": {
|
|
47
|
+
"title": {"tag": "plain_text", "content": title},
|
|
48
|
+
"template": template,
|
|
49
|
+
},
|
|
50
|
+
"elements": [
|
|
51
|
+
{"tag": "div", "text": {"tag": "lark_md", "content": markdown_content}}
|
|
52
|
+
],
|
|
53
|
+
}
|
|
54
|
+
payload = {"msg_type": "interactive", "card": card}
|
|
55
|
+
resp = requests.post(
|
|
56
|
+
self.webhook_url, headers=headers, data=json.dumps(payload)
|
|
57
|
+
)
|
|
58
|
+
return bool(resp.ok and resp.json().get("StatusCode") == 0)
|
|
59
|
+
|
|
60
|
+
async def send_markdown_async(
|
|
61
|
+
self,
|
|
62
|
+
markdown_content: str,
|
|
63
|
+
title: str,
|
|
64
|
+
template: str = "turquoise",
|
|
65
|
+
):
|
|
66
|
+
return await asyncio.to_thread(
|
|
67
|
+
self.send_markdown, markdown_content, title, template
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def send_table(
|
|
71
|
+
self,
|
|
72
|
+
headers: list,
|
|
73
|
+
rows: list,
|
|
74
|
+
title: str,
|
|
75
|
+
template: str = "turquoise",
|
|
76
|
+
):
|
|
77
|
+
headers_req = {"Content-Type": "application/json; charset=utf-8"}
|
|
78
|
+
n_cols = len(headers)
|
|
79
|
+
columns = []
|
|
80
|
+
for ci in range(n_cols):
|
|
81
|
+
elements_in_col = []
|
|
82
|
+
# 表头
|
|
83
|
+
elements_in_col.append(
|
|
84
|
+
{
|
|
85
|
+
"tag": "div",
|
|
86
|
+
"text": {"tag": "lark_md", "content": f"**{headers[ci]}**"},
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
# 每一行的该列内容
|
|
90
|
+
for row in rows:
|
|
91
|
+
cell = row[ci] if ci < len(row) else ""
|
|
92
|
+
elements_in_col.append(
|
|
93
|
+
{"tag": "div", "text": {"tag": "lark_md", "content": cell}}
|
|
94
|
+
)
|
|
95
|
+
columns.append(
|
|
96
|
+
{
|
|
97
|
+
"tag": "column",
|
|
98
|
+
"width": "weighted",
|
|
99
|
+
"weight": 1,
|
|
100
|
+
"elements": elements_in_col,
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
elements = [{"tag": "column_set", "columns": columns}]
|
|
105
|
+
|
|
106
|
+
card = {
|
|
107
|
+
"config": {"wide_screen_mode": True, "enable_forward": True},
|
|
108
|
+
"header": {
|
|
109
|
+
"title": {"tag": "plain_text", "content": title},
|
|
110
|
+
"template": template,
|
|
111
|
+
},
|
|
112
|
+
"elements": elements,
|
|
113
|
+
}
|
|
114
|
+
payload = {"msg_type": "interactive", "card": card}
|
|
115
|
+
|
|
116
|
+
resp = requests.post(
|
|
117
|
+
self.webhook_url, headers=headers_req, data=json.dumps(payload)
|
|
118
|
+
)
|
|
119
|
+
try:
|
|
120
|
+
print("feishu send_table resp:", resp.status_code, resp.json())
|
|
121
|
+
except Exception:
|
|
122
|
+
print("feishu send_table resp:", resp.status_code, resp.text)
|
|
123
|
+
return bool(resp.ok and resp.json().get("StatusCode") == 0)
|
|
124
|
+
|
|
125
|
+
async def send_table_async(
|
|
126
|
+
self,
|
|
127
|
+
headers: list[str],
|
|
128
|
+
rows: list[list[str]],
|
|
129
|
+
title: str,
|
|
130
|
+
template: str = "turquoise",
|
|
131
|
+
):
|
|
132
|
+
return await asyncio.to_thread(self.send_table, headers, rows, title, template)
|
arraylib/net/request.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from typing import Generator
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Request:
|
|
6
|
+
def __init__(self, trust_env: bool = False):
|
|
7
|
+
self.session = requests.Session()
|
|
8
|
+
self.session.trust_env = trust_env
|
|
9
|
+
|
|
10
|
+
def _post(
|
|
11
|
+
self,
|
|
12
|
+
url: str,
|
|
13
|
+
data: dict = {},
|
|
14
|
+
headers: dict = {"Content-Type": "application/json"},
|
|
15
|
+
timeout: int = 0,
|
|
16
|
+
):
|
|
17
|
+
kwargs = {
|
|
18
|
+
"url": url,
|
|
19
|
+
"json": data,
|
|
20
|
+
"headers": headers,
|
|
21
|
+
}
|
|
22
|
+
if timeout:
|
|
23
|
+
kwargs["timeout"] = timeout
|
|
24
|
+
response = self.session.post(**kwargs)
|
|
25
|
+
return response
|
|
26
|
+
|
|
27
|
+
def _get(self, url: str, params: dict = {}, headers: dict = {}, timeout: int = 0):
|
|
28
|
+
kwargs = {
|
|
29
|
+
"url": url,
|
|
30
|
+
"params": params,
|
|
31
|
+
"headers": headers,
|
|
32
|
+
}
|
|
33
|
+
if timeout:
|
|
34
|
+
kwargs["timeout"] = timeout
|
|
35
|
+
response = self.session.get(**kwargs)
|
|
36
|
+
return response
|
|
37
|
+
|
|
38
|
+
def post(
|
|
39
|
+
self,
|
|
40
|
+
url: str,
|
|
41
|
+
data: dict = {},
|
|
42
|
+
headers: dict = {"Content-Type": "application/json"},
|
|
43
|
+
timeout: int = 0,
|
|
44
|
+
) -> tuple[dict, bool]:
|
|
45
|
+
"""
|
|
46
|
+
description:
|
|
47
|
+
发送 POST 请求
|
|
48
|
+
parameters:
|
|
49
|
+
url(str): 请求 URL
|
|
50
|
+
data(dict): 请求数据
|
|
51
|
+
headers(dict): 请求头
|
|
52
|
+
timeout(int): 请求超时时间
|
|
53
|
+
return:
|
|
54
|
+
response(dict): 响应数据
|
|
55
|
+
flag(bool): 请求是否成功
|
|
56
|
+
"""
|
|
57
|
+
response = self._post(url, data, headers, timeout)
|
|
58
|
+
|
|
59
|
+
if response.status_code == 200:
|
|
60
|
+
return response.json(), True
|
|
61
|
+
else:
|
|
62
|
+
print(response.status_code, response.text)
|
|
63
|
+
return {
|
|
64
|
+
"status_code": response.status_code,
|
|
65
|
+
"message": response.text,
|
|
66
|
+
}, False
|
|
67
|
+
|
|
68
|
+
def get(
|
|
69
|
+
self,
|
|
70
|
+
url: str,
|
|
71
|
+
params: dict = {},
|
|
72
|
+
headers: dict = {},
|
|
73
|
+
timeout: int = 0,
|
|
74
|
+
) -> tuple[dict, bool]:
|
|
75
|
+
"""
|
|
76
|
+
description:
|
|
77
|
+
发送 GET 请求
|
|
78
|
+
parameters:
|
|
79
|
+
url(str): 请求 URL
|
|
80
|
+
params(dict): 请求参数
|
|
81
|
+
headers(dict): 请求头
|
|
82
|
+
timeout(int): 请求超时时间
|
|
83
|
+
return:
|
|
84
|
+
response(dict): 响应数据
|
|
85
|
+
flag(bool): 请求是否成功
|
|
86
|
+
"""
|
|
87
|
+
response = self._get(url, params, headers, timeout)
|
|
88
|
+
|
|
89
|
+
if response.status_code == 200:
|
|
90
|
+
return response.json(), True
|
|
91
|
+
else:
|
|
92
|
+
print(response.status_code, response.text)
|
|
93
|
+
return {
|
|
94
|
+
"status_code": response.status_code,
|
|
95
|
+
"message": response.text,
|
|
96
|
+
}, False
|
|
97
|
+
|
|
98
|
+
def post_stream(
|
|
99
|
+
self,
|
|
100
|
+
url: str,
|
|
101
|
+
data: dict = {},
|
|
102
|
+
headers: dict = {"Content-Type": "application/json"},
|
|
103
|
+
timeout: int = 0,
|
|
104
|
+
) -> Generator[str, None, None]:
|
|
105
|
+
"""
|
|
106
|
+
description:
|
|
107
|
+
发送 POST 流式请求
|
|
108
|
+
parameters:
|
|
109
|
+
url(str): 请求 URL
|
|
110
|
+
data(dict): 请求数据
|
|
111
|
+
headers(dict): 请求头
|
|
112
|
+
timeout(int): 请求超时时间
|
|
113
|
+
return:
|
|
114
|
+
response(generator): 响应数据生成器
|
|
115
|
+
"""
|
|
116
|
+
response = self._post(url, data, headers, timeout)
|
|
117
|
+
if response.status_code == 200:
|
|
118
|
+
return response.iter_lines()
|
|
119
|
+
|
|
120
|
+
def get_stream(
|
|
121
|
+
self,
|
|
122
|
+
url: str,
|
|
123
|
+
params: dict = {},
|
|
124
|
+
headers: dict = {},
|
|
125
|
+
timeout: int = 0,
|
|
126
|
+
) -> Generator[str, None, None]:
|
|
127
|
+
"""
|
|
128
|
+
description:
|
|
129
|
+
发送 GET 流式请求
|
|
130
|
+
parameters:
|
|
131
|
+
url(str): 请求 URL
|
|
132
|
+
params(dict): 请求参数
|
|
133
|
+
headers(dict): 请求头
|
|
134
|
+
timeout(int): 请求超时时间
|
|
135
|
+
return:
|
|
136
|
+
response(generator): 响应数据生成器
|
|
137
|
+
"""
|
|
138
|
+
response = self._get(url, params, headers, timeout)
|
|
139
|
+
if response.status_code == 200:
|
|
140
|
+
return response.iter_lines()
|
|
141
|
+
|
|
142
|
+
def __del__(self):
|
|
143
|
+
self.session.close()
|