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.
@@ -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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
dtsdance/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """
2
+ client 模块 - 各种API服务的封装
3
+ """
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)}")