dts-dance 0.2.4__tar.gz → 0.2.5__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.
- {dts_dance-0.2.4 → dts_dance-0.2.5}/PKG-INFO +2 -2
- {dts_dance-0.2.4 → dts_dance-0.2.5}/config.yaml +5 -5
- dts_dance-0.2.5/dtsdance/ddutil.py +39 -0
- {dts_dance-0.2.4 → dts_dance-0.2.5}/dtsdance/dflow.py +47 -46
- {dts_dance-0.2.4 → dts_dance-0.2.5}/dtsdance/dsyncer.py +11 -47
- {dts_dance-0.2.4 → dts_dance-0.2.5}/dtsdance/feishu_base.py +44 -7
- {dts_dance-0.2.4 → dts_dance-0.2.5}/dtsdance/tcc_inner.py +2 -2
- {dts_dance-0.2.4 → dts_dance-0.2.5}/pyproject.toml +2 -2
- dts_dance-0.2.5/tests/test_dflow.py +34 -0
- {dts_dance-0.2.4 → dts_dance-0.2.5}/tests/test_spacex.py +7 -3
- {dts_dance-0.2.4 → dts_dance-0.2.5}/tests/test_tcc.py +9 -4
- {dts_dance-0.2.4 → dts_dance-0.2.5}/uv.lock +2 -2
- dts_dance-0.2.4/tests/test_dflow.py +0 -17
- {dts_dance-0.2.4 → dts_dance-0.2.5}/.gitignore +0 -0
- {dts_dance-0.2.4 → dts_dance-0.2.5}/.python-version +0 -0
- {dts_dance-0.2.4 → dts_dance-0.2.5}/CLAUDE.md +0 -0
- {dts_dance-0.2.4 → dts_dance-0.2.5}/DEV.md +0 -0
- {dts_dance-0.2.4 → dts_dance-0.2.5}/README.md +0 -0
- {dts_dance-0.2.4 → dts_dance-0.2.5}/dtsdance/__init__.py +0 -0
- {dts_dance-0.2.4 → dts_dance-0.2.5}/dtsdance/bytecloud.py +0 -0
- {dts_dance-0.2.4 → dts_dance-0.2.5}/dtsdance/feishu_table.py +0 -0
- {dts_dance-0.2.4 → dts_dance-0.2.5}/dtsdance/metrics_fe.py +0 -0
- {dts_dance-0.2.4 → dts_dance-0.2.5}/dtsdance/s3.py +0 -0
- dts_dance-0.2.4/dtsdance/spacex_bytedts.py → dts_dance-0.2.5/dtsdance/spacex.py +0 -0
- {dts_dance-0.2.4 → dts_dance-0.2.5}/dtsdance/tcc_open.py +1 -1
- {dts_dance-0.2.4 → dts_dance-0.2.5}/tests/config.py +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dts-dance
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: dts dance lib
|
|
5
5
|
Keywords: observation,tools
|
|
6
6
|
Requires-Python: >=3.12
|
|
7
|
-
Requires-Dist: boto3
|
|
7
|
+
Requires-Dist: boto3>=1.42.30
|
|
8
8
|
Requires-Dist: loguru<0.8.0,>=0.7.3
|
|
9
9
|
Requires-Dist: pyyaml<7.0.0,>=6.0.3
|
|
10
10
|
Requires-Dist: requests-toolbelt==1.0.0
|
|
@@ -29,8 +29,8 @@ sites:
|
|
|
29
29
|
endpoint_bytedts_spacex: https://bytedts-spacex-usttp-api.tiktok-us.org
|
|
30
30
|
svc_account: bytedts_spacex
|
|
31
31
|
svc_secret: 70a99c2a17599c0335527e82b3d1381c
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
eu-ttp:
|
|
33
|
+
endpoint: https://bc-iedt-gw.tiktok-eu.net
|
|
34
|
+
endpoint_bytedts_spacex: https://bytedts-spacex-eu.tiktok-eu.org
|
|
35
|
+
svc_account: bytedts_spacex
|
|
36
|
+
svc_secret: 70a99c2a17599c0335527e82b3d1381c
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from typing import Any, Optional
|
|
2
|
+
from loguru import logger
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def make_request(method: str, url: str, headers: dict[str, str], payload: Optional[dict] = None) -> dict[str, Any]:
|
|
7
|
+
"""
|
|
8
|
+
发送 HTTP 请求的通用方法
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
method: HTTP 方法 (GET/POST)
|
|
12
|
+
url: 请求 URL
|
|
13
|
+
headers: 请求头
|
|
14
|
+
payload: POST 请求的 JSON 数据
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
dict[str, Any]: 解析后的 JSON 响应
|
|
18
|
+
"""
|
|
19
|
+
response = None
|
|
20
|
+
try:
|
|
21
|
+
if method.upper() == "GET":
|
|
22
|
+
response = requests.get(url, headers=headers)
|
|
23
|
+
elif method.upper() == "POST":
|
|
24
|
+
response = requests.post(url, json=payload, headers=headers)
|
|
25
|
+
else:
|
|
26
|
+
raise ValueError(f"不支持的 HTTP 方法: {method}")
|
|
27
|
+
|
|
28
|
+
# 检查响应状态码
|
|
29
|
+
# response.raise_for_status()
|
|
30
|
+
|
|
31
|
+
# 解析 JSON 响应
|
|
32
|
+
return response.json()
|
|
33
|
+
|
|
34
|
+
except Exception as e:
|
|
35
|
+
error_msg = f"make_request occur error, error: {e}"
|
|
36
|
+
if response is not None:
|
|
37
|
+
error_msg += f", response.text: {response.text}"
|
|
38
|
+
logger.warning(error_msg)
|
|
39
|
+
raise
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import collections
|
|
2
|
+
from typing import Any, cast
|
|
2
3
|
from loguru import logger
|
|
4
|
+
|
|
5
|
+
from dtsdance.ddutil import make_request
|
|
3
6
|
from .bytecloud import ByteCloudClient
|
|
4
|
-
import requests
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
class TaskNotFound(Exception):
|
|
@@ -17,42 +19,13 @@ class DFlowClient:
|
|
|
17
19
|
def __init__(self, bytecloud_client: ByteCloudClient) -> None:
|
|
18
20
|
self.bytecloud_client = bytecloud_client
|
|
19
21
|
|
|
20
|
-
def
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
def build_request_headers(self, site: str, vregion: str | None) -> dict[str, str]:
|
|
23
|
+
headers = self.bytecloud_client.build_request_headers(site)
|
|
24
|
+
if vregion:
|
|
25
|
+
headers["x-bcgw-vregion"] = vregion
|
|
26
|
+
return headers
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
method: HTTP 方法 (GET/POST)
|
|
26
|
-
url: 请求 URL
|
|
27
|
-
headers: 请求头
|
|
28
|
-
json_data: POST 请求的 JSON 数据
|
|
29
|
-
|
|
30
|
-
Returns:
|
|
31
|
-
dict[str, Any]: 解析后的 JSON 响应
|
|
32
|
-
"""
|
|
33
|
-
response = None
|
|
34
|
-
try:
|
|
35
|
-
if method.upper() == "GET":
|
|
36
|
-
response = requests.get(url, headers=headers)
|
|
37
|
-
elif method.upper() == "POST":
|
|
38
|
-
response = requests.post(url, json=json_data, headers=headers)
|
|
39
|
-
else:
|
|
40
|
-
raise ValueError(f"不支持的 HTTP 方法: {method}")
|
|
41
|
-
|
|
42
|
-
# 检查响应状态码
|
|
43
|
-
response.raise_for_status()
|
|
44
|
-
|
|
45
|
-
# 解析 JSON 响应
|
|
46
|
-
return response.json()
|
|
47
|
-
|
|
48
|
-
except Exception as e:
|
|
49
|
-
error_msg = f"_make_request occur error, error: {e}"
|
|
50
|
-
if response is not None:
|
|
51
|
-
error_msg += f", response.text: {response.text}"
|
|
52
|
-
logger.warning(error_msg)
|
|
53
|
-
raise
|
|
54
|
-
|
|
55
|
-
def get_task_info(self, site: str, task_id: str) -> dict[str, Any]:
|
|
28
|
+
def get_task_info(self, site: str, task_id: str, vregion: str | None = None) -> dict[str, Any]:
|
|
56
29
|
"""
|
|
57
30
|
获取 DFlow 任务信息
|
|
58
31
|
|
|
@@ -68,9 +41,9 @@ class DFlowClient:
|
|
|
68
41
|
url = f"{site_info.endpoint}/api/v1/bytedts/api/bytedts/v3/DescribeTaskInfo"
|
|
69
42
|
|
|
70
43
|
# 构建请求数据
|
|
71
|
-
|
|
44
|
+
payload = {"id": int(task_id)}
|
|
72
45
|
|
|
73
|
-
response_data =
|
|
46
|
+
response_data = make_request("POST", url, self.build_request_headers(site, vregion), payload)
|
|
74
47
|
|
|
75
48
|
message = response_data.get("message")
|
|
76
49
|
# logger.debug(f"get_task_info {site} {task_id}, message: {message}")
|
|
@@ -94,7 +67,7 @@ class DFlowClient:
|
|
|
94
67
|
except (KeyError, AttributeError, Exception) as e:
|
|
95
68
|
raise Exception(f"无法从响应中提取 DFlow 任务信息数据: {str(e)}")
|
|
96
69
|
|
|
97
|
-
def get_dflow_info(self, site: str, dflow_id: str) -> dict[str, Any]:
|
|
70
|
+
def get_dflow_info(self, site: str, dflow_id: str, vregion: str | None = None) -> dict[str, Any]:
|
|
98
71
|
"""
|
|
99
72
|
获取 DFlow 进程信息
|
|
100
73
|
|
|
@@ -110,9 +83,9 @@ class DFlowClient:
|
|
|
110
83
|
url = f"{site_info.endpoint}/api/v1/bytedts/api/bytedts/v3/DescribeDFlowDetail"
|
|
111
84
|
|
|
112
85
|
# 构建请求数据
|
|
113
|
-
|
|
86
|
+
payload = {"dflow_id": int(dflow_id)}
|
|
114
87
|
|
|
115
|
-
response_data =
|
|
88
|
+
response_data = make_request("POST", url, self.build_request_headers(site, vregion), payload)
|
|
116
89
|
|
|
117
90
|
message = response_data.get("message", "")
|
|
118
91
|
# logger.debug(f"get_dflow_info {site} {dflow_id}, message: {message}")
|
|
@@ -168,9 +141,9 @@ class DFlowClient:
|
|
|
168
141
|
url = f"{site_info.endpoint}/api/v1/bytedts/api/bytedts/v3/InitSystemResource"
|
|
169
142
|
|
|
170
143
|
# 构建请求数据
|
|
171
|
-
|
|
144
|
+
payload = {"ctrl_env": ctrl_env}
|
|
172
145
|
|
|
173
|
-
response_data =
|
|
146
|
+
response_data = make_request("POST", url, self.build_request_headers(site, None), payload)
|
|
174
147
|
|
|
175
148
|
message = response_data.get("message")
|
|
176
149
|
logger.info(f"int_resources {site} {ctrl_env}, message: {message}")
|
|
@@ -193,9 +166,9 @@ class DFlowClient:
|
|
|
193
166
|
url = f"{site_info.endpoint}/api/v1/bytedts/api/bytedts/v3/DescribeResources"
|
|
194
167
|
|
|
195
168
|
# 构建请求数据
|
|
196
|
-
|
|
169
|
+
payload = {"offset": 0, "limit": 10, "ctrl_env": ctrl_env}
|
|
197
170
|
|
|
198
|
-
response_data =
|
|
171
|
+
response_data = make_request("POST", url, self.build_request_headers(site, None), payload)
|
|
199
172
|
|
|
200
173
|
try:
|
|
201
174
|
data = response_data.get("data", {})
|
|
@@ -203,3 +176,31 @@ class DFlowClient:
|
|
|
203
176
|
return [item["name"] for item in items]
|
|
204
177
|
except (KeyError, AttributeError, Exception) as e:
|
|
205
178
|
raise Exception(f"无法从响应中提取 CTRL 环境资源列表数据: {str(e)}")
|
|
179
|
+
|
|
180
|
+
def get_all_ctrl_envs(self, site: str) -> dict[str, list[str]]:
|
|
181
|
+
"""获取所有可用的控制环境列表,返回以domain为键,ctrl_env列表为值的字典"""
|
|
182
|
+
site_info = self.bytecloud_client.get_site_config(site)
|
|
183
|
+
url = f"{site_info.endpoint}/api/v1/bytedts/api/bytedts/v3/DescribeRegions"
|
|
184
|
+
resp_json = make_request("POST", url, self.build_request_headers(site, None), {})
|
|
185
|
+
if resp_json["code"] != 0:
|
|
186
|
+
raise Exception(resp_json["message"])
|
|
187
|
+
|
|
188
|
+
# print(f"Fetched regions info: {json.dumps(resp_json["data"], ensure_ascii=False, indent=2)}")
|
|
189
|
+
|
|
190
|
+
# 提取所有控制环境列表,格式为以domain为键,ctrl_env列表为值的字典
|
|
191
|
+
all_ctrl_envs_by_domain = collections.defaultdict(list[str])
|
|
192
|
+
|
|
193
|
+
# 从ops_platform_region中提取ctrl_env_list
|
|
194
|
+
if "bytedts_env" in resp_json["data"]:
|
|
195
|
+
for bytedts_env in resp_json["data"]["bytedts_env"]:
|
|
196
|
+
if "ctrl_env_list" in bytedts_env and "domain" in bytedts_env:
|
|
197
|
+
env = bytedts_env["env"]
|
|
198
|
+
region = bytedts_env["include_region"][0] if bytedts_env["include_region"] else "Unknown"
|
|
199
|
+
env_region_key = env + "|" + region
|
|
200
|
+
for ctrl_env_item in bytedts_env["ctrl_env_list"]:
|
|
201
|
+
if "ctrl_env" in ctrl_env_item:
|
|
202
|
+
ctrl_env = ctrl_env_item["ctrl_env"]
|
|
203
|
+
# 将ctrl_env添加到domain对应的列表中
|
|
204
|
+
all_ctrl_envs_by_domain[env_region_key].append(ctrl_env)
|
|
205
|
+
|
|
206
|
+
return dict(all_ctrl_envs_by_domain)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
from dtsdance.ddutil import make_request
|
|
1
2
|
from .bytecloud import ByteCloudClient
|
|
2
3
|
from typing import Any, NamedTuple, Tuple, cast, Optional
|
|
3
4
|
from loguru import logger
|
|
4
|
-
import requests
|
|
5
5
|
import re
|
|
6
6
|
from datetime import datetime
|
|
7
7
|
|
|
@@ -41,42 +41,6 @@ class DSyncerClient:
|
|
|
41
41
|
|
|
42
42
|
return headers
|
|
43
43
|
|
|
44
|
-
def _make_request(self, method: str, url: str, headers: dict[str, str], data_raw: Optional[dict] = None) -> dict[str, Any]:
|
|
45
|
-
"""
|
|
46
|
-
发送 HTTP 请求的通用方法
|
|
47
|
-
|
|
48
|
-
Args:
|
|
49
|
-
method: HTTP 方法 (GET/POST)
|
|
50
|
-
url: 请求 URL
|
|
51
|
-
headers: 请求头
|
|
52
|
-
json_data: POST 请求的 JSON 数据
|
|
53
|
-
operation_name: 操作名称,用于日志记录
|
|
54
|
-
|
|
55
|
-
Returns:
|
|
56
|
-
dict[str, Any]: 解析后的 JSON 响应
|
|
57
|
-
"""
|
|
58
|
-
response = None
|
|
59
|
-
try:
|
|
60
|
-
if method.upper() == "GET":
|
|
61
|
-
response = requests.get(url, headers=headers)
|
|
62
|
-
elif method.upper() == "POST":
|
|
63
|
-
response = requests.post(url, json=data_raw, headers=headers)
|
|
64
|
-
else:
|
|
65
|
-
raise ValueError(f"不支持的 HTTP 方法: {method}")
|
|
66
|
-
|
|
67
|
-
# 检查响应状态码
|
|
68
|
-
# response.raise_for_status()
|
|
69
|
-
|
|
70
|
-
# 解析 JSON 响应
|
|
71
|
-
return response.json()
|
|
72
|
-
|
|
73
|
-
except Exception as e:
|
|
74
|
-
error_msg = f"_make_request occur error, error: {e}"
|
|
75
|
-
if response is not None:
|
|
76
|
-
error_msg += f", response.text: {response.text}"
|
|
77
|
-
logger.warning(error_msg)
|
|
78
|
-
raise
|
|
79
|
-
|
|
80
44
|
def _acquire_task_info(self, site: str, task_id: str) -> dict[str, str]:
|
|
81
45
|
"""
|
|
82
46
|
获取 DSyncer 任务信息
|
|
@@ -95,7 +59,7 @@ class DSyncerClient:
|
|
|
95
59
|
# 准备请求头
|
|
96
60
|
headers = self._build_headers(site)
|
|
97
61
|
|
|
98
|
-
return
|
|
62
|
+
return make_request("GET", url, headers)
|
|
99
63
|
|
|
100
64
|
def get_dflow_task_info(self, site: str, task_id: str) -> tuple[str, str]:
|
|
101
65
|
"""
|
|
@@ -108,7 +72,7 @@ class DSyncerClient:
|
|
|
108
72
|
# 准备请求头
|
|
109
73
|
headers = self._build_headers(site)
|
|
110
74
|
|
|
111
|
-
json_data =
|
|
75
|
+
json_data = make_request("GET", url, headers)
|
|
112
76
|
|
|
113
77
|
message = json_data.get("message", "")
|
|
114
78
|
logger.debug(f"get task migrate info {site} {task_id}, message: {message}")
|
|
@@ -214,9 +178,9 @@ class DSyncerClient:
|
|
|
214
178
|
# 准备请求头
|
|
215
179
|
headers = self._build_headers(site, secret_api=True)
|
|
216
180
|
|
|
217
|
-
|
|
181
|
+
payload = {"task_id": task_id}
|
|
218
182
|
|
|
219
|
-
response_data =
|
|
183
|
+
response_data = make_request("POST", url, headers, payload)
|
|
220
184
|
|
|
221
185
|
message = response_data.get("message", "")
|
|
222
186
|
logger.debug(f"get task migrate status {site} {task_id}, message: {message}")
|
|
@@ -236,9 +200,9 @@ class DSyncerClient:
|
|
|
236
200
|
# 准备请求头
|
|
237
201
|
headers = self._build_headers(site, secret_api=True)
|
|
238
202
|
|
|
239
|
-
|
|
203
|
+
payload = {"task_id_list": [task_id]}
|
|
240
204
|
|
|
241
|
-
response_data =
|
|
205
|
+
response_data = make_request("POST", url, headers, payload)
|
|
242
206
|
|
|
243
207
|
logger.debug(f"migration_mark_rollback return {site} {task_id}, json_data: {response_data}")
|
|
244
208
|
success_task = response_data.get("data", {}).get("success_task", [])
|
|
@@ -255,9 +219,9 @@ class DSyncerClient:
|
|
|
255
219
|
# 准备请求头
|
|
256
220
|
headers = self._build_headers(site, secret_api=True)
|
|
257
221
|
|
|
258
|
-
|
|
222
|
+
payload = {"task_id": task_id}
|
|
259
223
|
|
|
260
|
-
response_data =
|
|
224
|
+
response_data = make_request("POST", url, headers, payload)
|
|
261
225
|
|
|
262
226
|
logger.debug(f"migration_mark_success return {site} {task_id}, json_data: {response_data}")
|
|
263
227
|
success_task = response_data.get("data", {}).get("success_task", [])
|
|
@@ -277,7 +241,7 @@ class DSyncerClient:
|
|
|
277
241
|
# 准备请求头
|
|
278
242
|
headers = self._build_headers(site, secret_api=True)
|
|
279
243
|
|
|
280
|
-
|
|
244
|
+
payload = {
|
|
281
245
|
"task_id": task_id,
|
|
282
246
|
"delay_threshold": "20s",
|
|
283
247
|
"dsyncer_task_pause_threshold": "180s",
|
|
@@ -289,7 +253,7 @@ class DSyncerClient:
|
|
|
289
253
|
"app_parallel": app_parallel,
|
|
290
254
|
}
|
|
291
255
|
|
|
292
|
-
response_data =
|
|
256
|
+
response_data = make_request("POST", url, headers, payload)
|
|
293
257
|
|
|
294
258
|
logger.debug(f"migrate_task return {site} {task_id}, json_data: {response_data}")
|
|
295
259
|
message = response_data.get("message")
|
|
@@ -100,13 +100,15 @@ class FeishuBase:
|
|
|
100
100
|
headers["Authorization"] = f"Bearer {token}"
|
|
101
101
|
kwargs["headers"] = headers
|
|
102
102
|
|
|
103
|
+
response: requests.Response | None = None
|
|
103
104
|
try:
|
|
104
105
|
response = requests.request(method, url, **kwargs)
|
|
105
|
-
logger.debug(f"response_json: {response.json()}")
|
|
106
|
+
# logger.debug(f"response_json: {response.json()}")
|
|
106
107
|
response.raise_for_status()
|
|
107
108
|
return response
|
|
108
109
|
except requests.RequestException as e:
|
|
109
|
-
|
|
110
|
+
error_detail = response.json() if response is not None else "无响应"
|
|
111
|
+
logger.warning(f"API请求失败: {method} {url}, error: {e}, response_json: {error_detail}")
|
|
110
112
|
raise
|
|
111
113
|
|
|
112
114
|
def upload_image(self, image_path: str) -> str | None:
|
|
@@ -265,7 +267,7 @@ class FeishuBase:
|
|
|
265
267
|
logger.warning(f"回复消息失败: {e}")
|
|
266
268
|
raise
|
|
267
269
|
|
|
268
|
-
def batch_get_user_ids(self, emails: list[str]) ->
|
|
270
|
+
def batch_get_user_ids(self, emails: list[str]) -> dict[str, None | str]:
|
|
269
271
|
"""
|
|
270
272
|
批量获取用户ID
|
|
271
273
|
|
|
@@ -273,7 +275,7 @@ class FeishuBase:
|
|
|
273
275
|
emails: 用户邮箱列表
|
|
274
276
|
|
|
275
277
|
Returns:
|
|
276
|
-
|
|
278
|
+
批量获取结果(email -> user_id 的字典映射)
|
|
277
279
|
|
|
278
280
|
Raises:
|
|
279
281
|
Exception: 获取失败
|
|
@@ -281,16 +283,51 @@ class FeishuBase:
|
|
|
281
283
|
payload = {"emails": emails, "include_resigned": False}
|
|
282
284
|
|
|
283
285
|
try:
|
|
284
|
-
response = self.make_request("POST",
|
|
286
|
+
response = self.make_request("POST", "/contact/v3/users/batch_get_id?user_id_type=user_id", json=payload)
|
|
285
287
|
result = response.json()
|
|
286
288
|
if result.get("code") != 0:
|
|
287
289
|
raise Exception(f"请求失败: {result.get('msg')}")
|
|
288
290
|
|
|
289
|
-
|
|
291
|
+
user_dict = {user.get("email"): user.get("user_id", None) for user in result.get("data", {}).get("user_list", []) if user.get("email")}
|
|
290
292
|
|
|
291
293
|
logger.debug(f"发送响应logid: {response.headers.get('X-Tt-Logid')}")
|
|
292
294
|
|
|
293
|
-
return
|
|
295
|
+
return user_dict
|
|
294
296
|
except Exception as e:
|
|
295
297
|
logger.warning(f"批量获取用户ID失败: {e}")
|
|
296
298
|
raise
|
|
299
|
+
|
|
300
|
+
def batch_get_all_user_ids(self, emails: list[str]) -> dict[str, None | str]:
|
|
301
|
+
"""
|
|
302
|
+
批量获取所有用户ID,自动分批处理(每批最多50个)
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
emails: 用户邮箱列表
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
所有用户ID字典(email -> user_id 的映射)
|
|
309
|
+
|
|
310
|
+
Raises:
|
|
311
|
+
Exception: 获取失败
|
|
312
|
+
"""
|
|
313
|
+
if not emails:
|
|
314
|
+
return {}
|
|
315
|
+
|
|
316
|
+
all_user_dict = {}
|
|
317
|
+
batch_size = 50
|
|
318
|
+
|
|
319
|
+
# 分批处理
|
|
320
|
+
for i in range(0, len(emails), batch_size):
|
|
321
|
+
batch_emails = emails[i : i + batch_size]
|
|
322
|
+
logger.info(f"正在获取第 {i // batch_size + 1} 批用户ID,数量: {len(batch_emails)}")
|
|
323
|
+
|
|
324
|
+
try:
|
|
325
|
+
batch_user_dict = self.batch_get_user_ids(batch_emails)
|
|
326
|
+
all_user_dict.update(batch_user_dict)
|
|
327
|
+
except Exception as e:
|
|
328
|
+
logger.error(f"获取第 {i // batch_size + 1} 批用户ID失败: {e}")
|
|
329
|
+
# 继续处理下一批,不中断整个流程
|
|
330
|
+
continue
|
|
331
|
+
|
|
332
|
+
logger.info(f"共获取到 {len(all_user_dict)} 个用户ID")
|
|
333
|
+
return all_user_dict
|
|
@@ -20,7 +20,7 @@ class TCCInnerClient:
|
|
|
20
20
|
region: str,
|
|
21
21
|
dir: str,
|
|
22
22
|
conf_name: str,
|
|
23
|
-
) -> dict[str, Any]:
|
|
23
|
+
) -> list[dict[str, Any]]:
|
|
24
24
|
"""
|
|
25
25
|
List TCC configurations.
|
|
26
26
|
"""
|
|
@@ -49,7 +49,7 @@ class TCCInnerClient:
|
|
|
49
49
|
if base_resp.get("error_code", 0) != 0:
|
|
50
50
|
raise TCCError(f"TCC API error: {base_resp.get('error_message', 'Unknown error')}")
|
|
51
51
|
|
|
52
|
-
return result.get("data",
|
|
52
|
+
return result.get("data", [])
|
|
53
53
|
|
|
54
54
|
except requests.RequestException as e:
|
|
55
55
|
raise TCCError(f"Failed to get TCC config: {str(e)}") from e
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dts-dance"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.5"
|
|
8
8
|
description = "dts dance lib"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
keywords = ["tools", "observation"]
|
|
@@ -12,7 +12,7 @@ requires-python = ">=3.12"
|
|
|
12
12
|
dependencies = [
|
|
13
13
|
"requests>=2.32.5,<3.0.0",
|
|
14
14
|
"loguru>=0.7.3,<0.8.0",
|
|
15
|
-
"boto3>=1.42.30
|
|
15
|
+
"boto3>=1.42.30",
|
|
16
16
|
"pyyaml>=6.0.3,<7.0.0",
|
|
17
17
|
"requests_toolbelt==1.0.0",
|
|
18
18
|
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from dtsdance.dflow import DFlowClient
|
|
3
|
+
from dtsdance.bytecloud import ByteCloudClient
|
|
4
|
+
from config import ConfigLoader
|
|
5
|
+
|
|
6
|
+
loader = ConfigLoader.get_instance()
|
|
7
|
+
loader.load_from_file(["boe", "cn"])
|
|
8
|
+
site_configs = loader.get_site_configs()
|
|
9
|
+
bytecloud_client = ByteCloudClient(site_configs)
|
|
10
|
+
dflow_client = DFlowClient(bytecloud_client)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# pytest tests/test_dflow.py::test_get_task_info1 -s
|
|
14
|
+
def test_get_task_info1():
|
|
15
|
+
info = dflow_client.get_task_info("cn", "106037095986690")
|
|
16
|
+
print(f"DFlow Info: {info}")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# pytest tests/test_dflow.py::test_get_task_info2 -s
|
|
20
|
+
def test_get_task_info2():
|
|
21
|
+
info = dflow_client.get_task_info("cn", "99868429582521")
|
|
22
|
+
print(f"DFlow Info: {info}")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# pytest tests/test_dflow.py::test_get_dflow_info -s
|
|
26
|
+
def test_get_dflow_info():
|
|
27
|
+
info = dflow_client.get_dflow_info("cn", "106029334786818")
|
|
28
|
+
print(f"DFlow Info: {info}")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# pytest tests/test_dflow.py::test_get_all_ctrl_envs -s
|
|
32
|
+
def test_get_all_ctrl_envs():
|
|
33
|
+
ctrl_envs = dflow_client.get_all_ctrl_envs("cn")
|
|
34
|
+
print(f"get_all_ctrl_envs: {json.dumps(ctrl_envs, indent=2)}")
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from dtsdance.bytecloud import ByteCloudClient
|
|
3
|
-
from dtsdance.
|
|
3
|
+
from dtsdance.spacex import GatewayInfo, SpaceXClient
|
|
4
|
+
from config import ConfigLoader
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
loader = ConfigLoader.get_instance()
|
|
7
|
+
loader.load_from_file(["eu-ttp"])
|
|
8
|
+
site_configs = loader.get_site_configs()
|
|
9
|
+
bytecloud_client = ByteCloudClient(site_configs)
|
|
6
10
|
spacex = SpaceXClient(bytecloud_client)
|
|
7
11
|
|
|
8
12
|
|
|
9
13
|
# pytest tests/test_spacex.py::test_list_mgr -s
|
|
10
14
|
def test_list_mgr():
|
|
11
|
-
mgr_list = spacex.list_mgr("
|
|
15
|
+
mgr_list = spacex.list_mgr("eu-ttp")
|
|
12
16
|
print(f"mgr_list: {json.dumps(mgr_list, indent=2, ensure_ascii=False)}")
|
|
13
17
|
|
|
14
18
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import json
|
|
1
2
|
from dtsdance.bytecloud import ByteCloudClient
|
|
2
3
|
from dtsdance.tcc_open import TCCClient
|
|
3
4
|
from dtsdance.tcc_inner import TCCInnerClient
|
|
4
5
|
from config import ConfigLoader
|
|
5
6
|
|
|
6
7
|
loader = ConfigLoader.get_instance()
|
|
7
|
-
loader.load_from_file(["boe"])
|
|
8
|
+
loader.load_from_file(["boe", "us-ttp"])
|
|
9
|
+
# loader.load_from_file(["eu-ttp"])
|
|
8
10
|
site_configs = loader.get_site_configs()
|
|
9
11
|
|
|
10
12
|
|
|
@@ -18,7 +20,10 @@ def test_get_config():
|
|
|
18
20
|
|
|
19
21
|
# pytest tests/test_tcc.py::test_list_configs -s
|
|
20
22
|
def test_list_configs():
|
|
21
|
-
bytecloud_client = ByteCloudClient(
|
|
23
|
+
bytecloud_client = ByteCloudClient(site_configs)
|
|
22
24
|
client_inner = TCCInnerClient(bytecloud_client)
|
|
23
|
-
configs = client_inner.list_configs(site="boe", ns_name="bytedts.mgr.api", region="China-BOE", dir="/default", conf_name="
|
|
24
|
-
|
|
25
|
+
# configs = client_inner.list_configs(site="boe", ns_name="bytedts.mgr.api", region="China-BOE", dir="/default", conf_name="route")
|
|
26
|
+
configs = client_inner.list_configs(site="us-ttp", ns_name="bytedts.mgr.api", region="US-TTP", dir="/default", conf_name="route")
|
|
27
|
+
# configs = client_inner.list_configs(site="eu-ttp", ns_name="bytedts.mgr.api", region="EU-TTP", dir="/default", conf_name="route")
|
|
28
|
+
conf_names = [conf.get("conf_name", "") for conf in configs]
|
|
29
|
+
print(f"conf_names: {json.dumps(conf_names, ensure_ascii=False, indent=2)}")
|
|
@@ -107,7 +107,7 @@ wheels = [
|
|
|
107
107
|
|
|
108
108
|
[[package]]
|
|
109
109
|
name = "dts-dance"
|
|
110
|
-
version = "0.2.
|
|
110
|
+
version = "0.2.5"
|
|
111
111
|
source = { editable = "." }
|
|
112
112
|
dependencies = [
|
|
113
113
|
{ name = "boto3" },
|
|
@@ -119,7 +119,7 @@ dependencies = [
|
|
|
119
119
|
|
|
120
120
|
[package.metadata]
|
|
121
121
|
requires-dist = [
|
|
122
|
-
{ name = "boto3", specifier = ">=1.42.30
|
|
122
|
+
{ name = "boto3", specifier = ">=1.42.30" },
|
|
123
123
|
{ name = "loguru", specifier = ">=0.7.3,<0.8.0" },
|
|
124
124
|
{ name = "pyyaml", specifier = ">=6.0.3,<7.0.0" },
|
|
125
125
|
{ name = "requests", specifier = ">=2.32.5,<3.0.0" },
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
from dtsdance.dflow import DFlowClient
|
|
2
|
-
from dtsdance.bytecloud import ByteCloudClient
|
|
3
|
-
|
|
4
|
-
bytecloud_client = ByteCloudClient(False)
|
|
5
|
-
dflow_client = DFlowClient(bytecloud_client)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
# pytest tests/test_dflow.py::test_get_task_info -s
|
|
9
|
-
def test_get_task_info():
|
|
10
|
-
info = dflow_client.get_task_info("cn", "106037095986690")
|
|
11
|
-
print(f"DFlow Info: {info}")
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
# pytest tests/test_dflow.py::test_get_dflow_info -s
|
|
15
|
-
def test_get_dflow_info():
|
|
16
|
-
info = dflow_client.get_dflow_info("cn", "106029334786818")
|
|
17
|
-
print(f"DFlow Info: {info}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|