dts-dance 0.1.8__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.
- dts_dance-0.1.8.dist-info/METADATA +40 -0
- dts_dance-0.1.8.dist-info/RECORD +13 -0
- dts_dance-0.1.8.dist-info/WHEEL +4 -0
- dtsdance/__init__.py +3 -0
- dtsdance/bytecloud.py +163 -0
- dtsdance/dflow.py +173 -0
- dtsdance/dsyncer.py +301 -0
- dtsdance/feishu_base.py +107 -0
- dtsdance/feishu_table.py +224 -0
- dtsdance/metrics_fe.py +516 -0
- dtsdance/s3.py +40 -0
- dtsdance/spacex.py +69 -0
- dtsdance/tcc.py +234 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dts-dance
|
|
3
|
+
Version: 0.1.8
|
|
4
|
+
Summary: dts dance lib
|
|
5
|
+
Keywords: observation,tools
|
|
6
|
+
Requires-Python: >=3.12
|
|
7
|
+
Requires-Dist: loguru==0.7.3
|
|
8
|
+
Requires-Dist: pyyaml==6.0.3
|
|
9
|
+
Requires-Dist: requests==2.32.5
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# dts-dance
|
|
13
|
+
|
|
14
|
+
A Python client library providing convenient interfaces for various API services.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install dts-dance
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
- Multi-service API client implementations
|
|
25
|
+
- Automatic authentication token management
|
|
26
|
+
- Thread-safe token renewal
|
|
27
|
+
- Type-safe interfaces with full type hints
|
|
28
|
+
- Built-in error handling and logging
|
|
29
|
+
|
|
30
|
+
## Requirements
|
|
31
|
+
|
|
32
|
+
- Python 3.12+
|
|
33
|
+
|
|
34
|
+
## Development
|
|
35
|
+
|
|
36
|
+
See [DEV.md](DEV.md) for development instructions.
|
|
37
|
+
|
|
38
|
+
## License
|
|
39
|
+
|
|
40
|
+
MIT
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
dtsdance/__init__.py,sha256=Yl_jEZ5weYfcrklnDvwB4wSgCOvMBLRRgWx0gHs3qfM,49
|
|
2
|
+
dtsdance/bytecloud.py,sha256=joixJEQxX9k3VqFDNrccTVSI2WVdxdZ1JyuWFVF9zjo,4998
|
|
3
|
+
dtsdance/dflow.py,sha256=gow-zCX5lbQqqW2MRWvWynej7pZ_TQ5NmOb__j2aQT4,5497
|
|
4
|
+
dtsdance/dsyncer.py,sha256=1OateZLGekYP_EF9_oAHqsj4sdQm6ahIoRllEgT9Y3I,11142
|
|
5
|
+
dtsdance/feishu_base.py,sha256=2j4ZM4PFqJ-9EhC6DQ1OmAg--3VBGZyyRuxyjL0j6OU,3733
|
|
6
|
+
dtsdance/feishu_table.py,sha256=ZUeoKrM4nmm5hFhc3vWOVYeLP390orm-284Od92G4iQ,8424
|
|
7
|
+
dtsdance/metrics_fe.py,sha256=uPdbjGaaYBOv-rK7lKUx6aVT5Sj6ZkW9m06NyqMrPf0,18990
|
|
8
|
+
dtsdance/s3.py,sha256=EMY5uDnqRUuHnZQBkquLdQjLR5I51LuCpCPNsDz0Z0w,1340
|
|
9
|
+
dtsdance/spacex.py,sha256=wgbuwDTLXopJnLn2puX-9MfeVXvi_nQ3C9uN8McoJf4,2113
|
|
10
|
+
dtsdance/tcc.py,sha256=M_0cOVYyvUgjnC1uKWdz3YgRplafQUqX39gX_YEg8eE,6563
|
|
11
|
+
dts_dance-0.1.8.dist-info/METADATA,sha256=VfydsQQEln8dU_jYPfT7WQZDCHgrAyvEFKjg59V1nv0,735
|
|
12
|
+
dts_dance-0.1.8.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
13
|
+
dts_dance-0.1.8.dist-info/RECORD,,
|
dtsdance/__init__.py
ADDED
dtsdance/bytecloud.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import threading
|
|
3
|
+
import time
|
|
4
|
+
from typing import Dict
|
|
5
|
+
from loguru import logger
|
|
6
|
+
from typing import NamedTuple
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ByteCloudEnvInfo(NamedTuple):
|
|
10
|
+
name: str
|
|
11
|
+
endpoint: str
|
|
12
|
+
token: str
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ByteCloudHelper:
|
|
16
|
+
"""
|
|
17
|
+
ByteCloud Helper
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# 每小时刷新一次,单位为秒
|
|
21
|
+
_REFRESH_INTERVAL = 1 * 60 * 60
|
|
22
|
+
|
|
23
|
+
def __init__(self, envs: dict[str, ByteCloudEnvInfo]):
|
|
24
|
+
"""
|
|
25
|
+
初始化 ByteCloud Helper
|
|
26
|
+
从配置文件加载所有环境的信息,并为每个环境初始化 JWT 令牌
|
|
27
|
+
envs 中保存内容 list[(name, endpoint, token)]
|
|
28
|
+
"""
|
|
29
|
+
self.envs = envs
|
|
30
|
+
|
|
31
|
+
# 初始化线程锁,用于保护 jwt_tokens 的并发访问
|
|
32
|
+
self.token_lock = threading.Lock()
|
|
33
|
+
|
|
34
|
+
# 初始化 JWT 令牌缓存,按环境名称索引
|
|
35
|
+
self.jwt_tokens: Dict[str, str] = {}
|
|
36
|
+
|
|
37
|
+
# 更新所有环境的 JWT 令牌
|
|
38
|
+
self._refresh_tokens()
|
|
39
|
+
|
|
40
|
+
# 启动 JWT 令牌刷新线程
|
|
41
|
+
self._start_refresh_thread()
|
|
42
|
+
|
|
43
|
+
def _start_refresh_thread(self):
|
|
44
|
+
"""
|
|
45
|
+
启动一个后台线程,定期刷新所有环境的 JWT 令牌
|
|
46
|
+
"""
|
|
47
|
+
refresh_thread = threading.Thread(
|
|
48
|
+
target=self._refresh_token_periodically,
|
|
49
|
+
daemon=True,
|
|
50
|
+
name="jwt-token-refresh",
|
|
51
|
+
)
|
|
52
|
+
refresh_thread.start()
|
|
53
|
+
|
|
54
|
+
def _refresh_token_periodically(self):
|
|
55
|
+
"""
|
|
56
|
+
定期刷新所有环境的 JWT 令牌的线程函数
|
|
57
|
+
"""
|
|
58
|
+
while True:
|
|
59
|
+
# 等待指定时间
|
|
60
|
+
time.sleep(self._REFRESH_INTERVAL)
|
|
61
|
+
self._refresh_tokens()
|
|
62
|
+
|
|
63
|
+
def _refresh_tokens(self):
|
|
64
|
+
"""
|
|
65
|
+
刷新所有环境的 JWT 令牌
|
|
66
|
+
"""
|
|
67
|
+
logger.debug("开始刷新所有环境的 JWT 令牌...")
|
|
68
|
+
|
|
69
|
+
for _, env in self.envs.items():
|
|
70
|
+
try:
|
|
71
|
+
# 刷新令牌
|
|
72
|
+
new_token = self._acquire_jwt_token(env.endpoint, env.token)
|
|
73
|
+
# 使用线程锁更新缓存中的 JWT 令牌
|
|
74
|
+
with self.token_lock:
|
|
75
|
+
self.jwt_tokens[env.name] = new_token
|
|
76
|
+
logger.debug(f"环境 {env.name} 的 JWT 令牌成功刷新,新令牌: {new_token}")
|
|
77
|
+
except Exception as e:
|
|
78
|
+
logger.error(f"环境 {env.name} 的 JWT 令牌刷新失败: {e}")
|
|
79
|
+
|
|
80
|
+
logger.debug(f"所有环境的 JWT 令牌已成功刷新。jwt_tokens: {self.jwt_tokens}")
|
|
81
|
+
|
|
82
|
+
def _acquire_jwt_token(self, endpoint: str, token: str) -> str:
|
|
83
|
+
"""
|
|
84
|
+
获取 JWT 令牌
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
token: 认证令牌
|
|
88
|
+
endpoint: API 端点
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
str: JWT 令牌
|
|
92
|
+
"""
|
|
93
|
+
url = endpoint + "/auth/api/v1/jwt"
|
|
94
|
+
headers = {
|
|
95
|
+
"Content-Type": "application/json",
|
|
96
|
+
"Authorization": "Bearer " + token,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
# 发送 GET 请求
|
|
101
|
+
response = requests.get(url, headers=headers, timeout=60)
|
|
102
|
+
|
|
103
|
+
# 调试用途,输出返回内容,保存内容到本地文件
|
|
104
|
+
# logger.debug(f"获取JWT令牌响应: response.text={response.text}")
|
|
105
|
+
# with open("jwt_response.html", "w", encoding="utf-8") as f:
|
|
106
|
+
# f.write(response.text)
|
|
107
|
+
|
|
108
|
+
# 检查响应状态码
|
|
109
|
+
if response.status_code != 200:
|
|
110
|
+
raise Exception(f"获取JWT令牌失败。status_code: {response.status_code}, response.text: {response.text}")
|
|
111
|
+
|
|
112
|
+
# 解析响应体
|
|
113
|
+
response_json = response.json()
|
|
114
|
+
if response_json.get("code", -1) != 0:
|
|
115
|
+
raise Exception(f"获取JWT令牌失败: {response.text}")
|
|
116
|
+
|
|
117
|
+
# 从响应头中获取 JWT 令牌
|
|
118
|
+
jwt_token = response.headers.get("X-Jwt-Token")
|
|
119
|
+
if not jwt_token:
|
|
120
|
+
raise Exception("响应头中没有 X-Jwt-Token")
|
|
121
|
+
|
|
122
|
+
return jwt_token
|
|
123
|
+
|
|
124
|
+
except Exception as e:
|
|
125
|
+
logger.error(f"获取JWT令牌时出错: {e}")
|
|
126
|
+
raise
|
|
127
|
+
|
|
128
|
+
def get_jwt_token(self, env: str) -> str:
|
|
129
|
+
"""
|
|
130
|
+
获取指定环境的 JWT 令牌
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
env: 环境名称,如 'China-North', 'China-East', 'Singapore-Central' 等
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
str: 当前有效的 JWT 令牌
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
KeyError: 如果指定的环境不存在
|
|
140
|
+
"""
|
|
141
|
+
# 使用线程锁保护并发访问
|
|
142
|
+
with self.token_lock:
|
|
143
|
+
if env not in self.jwt_tokens:
|
|
144
|
+
raise KeyError(f"环境 {env} 的 JWT 令牌不存在")
|
|
145
|
+
return self.jwt_tokens[env]
|
|
146
|
+
|
|
147
|
+
def get_env_info(self, env: str) -> ByteCloudEnvInfo:
|
|
148
|
+
"""
|
|
149
|
+
获取指定环境的信息
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
env: 环境名称
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
ByteCloudEnvInfo: 指定环境的信息
|
|
156
|
+
|
|
157
|
+
Raises:
|
|
158
|
+
KeyError: 如果指定的环境不存在
|
|
159
|
+
"""
|
|
160
|
+
if env not in self.envs:
|
|
161
|
+
raise KeyError(f"环境 {env} 不存在")
|
|
162
|
+
|
|
163
|
+
return self.envs[env]
|
dtsdance/dflow.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
from typing import Any, cast, Optional
|
|
2
|
+
from loguru import logger
|
|
3
|
+
from .bytecloud import ByteCloudHelper
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DFlowHelper:
|
|
8
|
+
|
|
9
|
+
def __init__(self, bytecloud_helper: ByteCloudHelper) -> None:
|
|
10
|
+
self.bytecloud_helper = bytecloud_helper
|
|
11
|
+
|
|
12
|
+
def _build_headers(self, env: str) -> dict[str, str]:
|
|
13
|
+
"""
|
|
14
|
+
构建请求头
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
env: 环境名称
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
dict[str, str]: 请求头字典
|
|
21
|
+
"""
|
|
22
|
+
jwt_token = self.bytecloud_helper.get_jwt_token(env)
|
|
23
|
+
headers = {"x-jwt-token": jwt_token}
|
|
24
|
+
return headers
|
|
25
|
+
|
|
26
|
+
def _make_request(self, method: str, url: str, headers: dict[str, str], json_data: Optional[dict] = None) -> dict[str, Any]:
|
|
27
|
+
"""
|
|
28
|
+
发送 HTTP 请求的通用方法
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
method: HTTP 方法 (GET/POST)
|
|
32
|
+
url: 请求 URL
|
|
33
|
+
headers: 请求头
|
|
34
|
+
json_data: POST 请求的 JSON 数据
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
dict[str, Any]: 解析后的 JSON 响应
|
|
38
|
+
"""
|
|
39
|
+
response = None
|
|
40
|
+
try:
|
|
41
|
+
if method.upper() == "GET":
|
|
42
|
+
response = requests.get(url, headers=headers)
|
|
43
|
+
elif method.upper() == "POST":
|
|
44
|
+
response = requests.post(url, json=json_data, headers=headers)
|
|
45
|
+
else:
|
|
46
|
+
raise ValueError(f"不支持的 HTTP 方法: {method}")
|
|
47
|
+
|
|
48
|
+
# 检查响应状态码
|
|
49
|
+
response.raise_for_status()
|
|
50
|
+
|
|
51
|
+
# 解析 JSON 响应
|
|
52
|
+
return response.json()
|
|
53
|
+
|
|
54
|
+
except Exception as e:
|
|
55
|
+
error_msg = f"_make_request occur error, error: {e}"
|
|
56
|
+
if response is not None:
|
|
57
|
+
error_msg += f", response.text: {response.text}"
|
|
58
|
+
logger.warning(error_msg)
|
|
59
|
+
raise
|
|
60
|
+
|
|
61
|
+
def get_dflow_info(self, env: str, task_id: str) -> dict[str, Any]:
|
|
62
|
+
"""
|
|
63
|
+
获取 DFlow 任务信息
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
env: 环境名称
|
|
67
|
+
task_id: DFlow 任务 ID
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
dict[str, Any]: DFlow 任务信息,包含 create_time 等字段
|
|
71
|
+
"""
|
|
72
|
+
# 构建 API URL
|
|
73
|
+
env_info = self.bytecloud_helper.get_env_info(env)
|
|
74
|
+
url = f"{env_info.endpoint}/api/v1/bytedts/api/bytedts/v3/DescribeTaskInfo"
|
|
75
|
+
|
|
76
|
+
# 准备请求头
|
|
77
|
+
headers = self._build_headers(env)
|
|
78
|
+
|
|
79
|
+
# 构建请求数据
|
|
80
|
+
json_data = {"id": int(task_id)}
|
|
81
|
+
|
|
82
|
+
response_data = self._make_request("POST", url, headers, json_data)
|
|
83
|
+
|
|
84
|
+
logger.info(f"get_dflow_info {env} {task_id}, message: {response_data.get('message')}")
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
data = cast(dict, response_data.get("data", {}))
|
|
88
|
+
task = cast(dict, data.get("task", {}))
|
|
89
|
+
# 提取核心信息
|
|
90
|
+
filtered_data = {
|
|
91
|
+
"task_id": task.get("id", ""),
|
|
92
|
+
"status": task.get("status", ""),
|
|
93
|
+
"desc": task.get("desc", ""),
|
|
94
|
+
"create_time": task.get("create_time", 0),
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return filtered_data
|
|
98
|
+
|
|
99
|
+
except (KeyError, AttributeError, Exception) as e:
|
|
100
|
+
raise Exception(f"无法从响应中提取 DFlow 任务信息数据: {str(e)}")
|
|
101
|
+
|
|
102
|
+
def generate_task_url(self, env: str, task_id: str) -> str:
|
|
103
|
+
"""
|
|
104
|
+
获取 DFlow 任务详情页面的 URL
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
env: 环境名称
|
|
108
|
+
task_id: DFlow 任务 ID
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
str: DFlow 任务详情页面的 URL
|
|
112
|
+
"""
|
|
113
|
+
# 根据环境生成对应的 scope 参数
|
|
114
|
+
env_info = self.bytecloud_helper.get_env_info(env)
|
|
115
|
+
return f"{env_info.endpoint}/bytedts/datasync/detail/{task_id}?scope={env}"
|
|
116
|
+
|
|
117
|
+
def init_resources(self, env: str, ctrl_env: str) -> bool:
|
|
118
|
+
"""
|
|
119
|
+
初始化 CTRL 环境资源
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
env: 环境名称
|
|
123
|
+
ctrl_env: 控制环境
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
bool: CTRL 环境资源初始化结果
|
|
127
|
+
"""
|
|
128
|
+
# 构建 API URL
|
|
129
|
+
env_info = self.bytecloud_helper.get_env_info(env)
|
|
130
|
+
url = f"{env_info.endpoint}/api/v1/bytedts/api/bytedts/v3/InitSystemResource"
|
|
131
|
+
|
|
132
|
+
# 准备请求头
|
|
133
|
+
headers = self._build_headers(env)
|
|
134
|
+
|
|
135
|
+
# 构建请求数据
|
|
136
|
+
json_data = {"ctrl_env": ctrl_env}
|
|
137
|
+
|
|
138
|
+
response_data = self._make_request("POST", url, headers, json_data)
|
|
139
|
+
|
|
140
|
+
message = response_data.get("message")
|
|
141
|
+
logger.info(f"int_resources {env} {ctrl_env}, message: {message}")
|
|
142
|
+
|
|
143
|
+
return message == "ok"
|
|
144
|
+
|
|
145
|
+
def list_resources(self, env: str, ctrl_env: str) -> list[str]:
|
|
146
|
+
"""
|
|
147
|
+
列举 CTRL 环境资源列表
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
env: 环境名称
|
|
151
|
+
ctrl_env: 控制环境
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
list[str]: CTRL 环境资源列表
|
|
155
|
+
"""
|
|
156
|
+
# 构建 API URL
|
|
157
|
+
env_info = self.bytecloud_helper.get_env_info(env)
|
|
158
|
+
url = f"{env_info.endpoint}/api/v1/bytedts/api/bytedts/v3/DescribeResources"
|
|
159
|
+
|
|
160
|
+
# 准备请求头
|
|
161
|
+
headers = self._build_headers(env)
|
|
162
|
+
|
|
163
|
+
# 构建请求数据
|
|
164
|
+
json_data = {"offset": 0, "limit": 10, "ctrl_env": ctrl_env}
|
|
165
|
+
|
|
166
|
+
response_data = self._make_request("POST", url, headers, json_data)
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
data = cast(dict, response_data.get("data", {}))
|
|
170
|
+
items = cast(list, data.get("items", []))
|
|
171
|
+
return [item["name"] for item in items]
|
|
172
|
+
except (KeyError, AttributeError, Exception) as e:
|
|
173
|
+
raise Exception(f"无法从响应中提取 CTRL 环境资源列表数据: {str(e)}")
|