openbayes-cli 0.4.5__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.
Files changed (55) hide show
  1. bayes/__init__.py +0 -0
  2. bayes/client/__init__.py +0 -0
  3. bayes/client/base.py +20 -0
  4. bayes/client/dataset_version_client.py +91 -0
  5. bayes/client/gear_client.py +468 -0
  6. bayes/client/job_run_client.py +504 -0
  7. bayes/client/job_upload_client.py +114 -0
  8. bayes/client/org_client.py +67 -0
  9. bayes/client/resource_client.py +108 -0
  10. bayes/client/runtime_client.py +32 -0
  11. bayes/client/ssh_client.py +66 -0
  12. bayes/client/status_client.py +14 -0
  13. bayes/client/user_client.py +58 -0
  14. bayes/commands/__init__.py +0 -0
  15. bayes/commands/gear.py +691 -0
  16. bayes/commands/org.py +100 -0
  17. bayes/commands/ssh.py +70 -0
  18. bayes/error.py +9 -0
  19. bayes/model/__init__.py +0 -0
  20. bayes/model/dataset_version.py +14 -0
  21. bayes/model/file/__init__.py +0 -0
  22. bayes/model/file/data_bindings.py +42 -0
  23. bayes/model/file/openbayes_data.py +133 -0
  24. bayes/model/file/openbayes_gear.py +72 -0
  25. bayes/model/file/openbayes_ignore.py +75 -0
  26. bayes/model/file/openbayes_yaml.py +180 -0
  27. bayes/model/file/settings.py +157 -0
  28. bayes/model/party.py +35 -0
  29. bayes/model/resource.py +147 -0
  30. bayes/model/runtime.py +34 -0
  31. bayes/root.py +170 -0
  32. bayes/templates/openbayes_zh-Hans.yaml +134 -0
  33. bayes/templates/openbayesignore.yaml +3 -0
  34. bayes/usercases/__init__.py +0 -0
  35. bayes/usercases/archive_usecase.py +79 -0
  36. bayes/usercases/auth_usecase.py +35 -0
  37. bayes/usercases/dataset_version_usecase.py +61 -0
  38. bayes/usercases/disk_usecase.py +98 -0
  39. bayes/usercases/gear_download_usecese.py +113 -0
  40. bayes/usercases/gear_logs_usecase.py +44 -0
  41. bayes/usercases/gear_open_usecase.py +16 -0
  42. bayes/usercases/gear_run_usecase.py +123 -0
  43. bayes/usercases/gear_upload_usecase.py +248 -0
  44. bayes/usercases/gear_usecase.py +301 -0
  45. bayes/usercases/openbayes_data_usecase.py +73 -0
  46. bayes/usercases/org_usecase.py +85 -0
  47. bayes/usercases/resource_usecase.py +74 -0
  48. bayes/usercases/runtime_usecase.py +58 -0
  49. bayes/usercases/ssh_usecase.py +110 -0
  50. bayes/usercases/switch_usercase.py +19 -0
  51. bayes/utils.py +163 -0
  52. openbayes_cli-0.4.5.dist-info/METADATA +62 -0
  53. openbayes_cli-0.4.5.dist-info/RECORD +55 -0
  54. openbayes_cli-0.4.5.dist-info/WHEEL +4 -0
  55. openbayes_cli-0.4.5.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,44 @@
1
+ import json
2
+
3
+ import websocket
4
+ from websocket import WebSocketConnectionClosedException
5
+
6
+ from bayes.client import gear_client
7
+ from bayes.client.base import BayesGQLClient
8
+ from bayes.model.file.settings import BayesSettings
9
+
10
+
11
+ def get_logs(id, party_name):
12
+ default_env = BayesSettings().default_env
13
+ gql_client = BayesGQLClient(default_env.graphQL, default_env.token)
14
+ logs = gear_client.get_logs(gql_client, id, party_name)
15
+
16
+ return logs
17
+
18
+
19
+ def get_logs_follow(id, party_name):
20
+ default_env = BayesSettings().default_env
21
+ gql_client = BayesGQLClient(default_env.graphQL, default_env.token)
22
+
23
+ job = gear_client.get_job_by_id(gql_client, id, party_name)
24
+ if job:
25
+ ws_url = job.get_link_value("websocket")
26
+ if ws_url is None:
27
+ return None, "不存在"
28
+ # Establish WebSocket connection
29
+ ws = websocket.WebSocket()
30
+ ws.connect(ws_url)
31
+ return ws, None
32
+ else:
33
+ return None, "不存在"
34
+
35
+
36
+ def receive_logs(ws: websocket.WebSocket, job_id: str):
37
+ try:
38
+ data = ws.recv()
39
+ except (WebSocketConnectionClosedException, EOFError):
40
+ return None, f"容器 {job_id} 已关闭,没有更多日志"
41
+ except Exception as err:
42
+ return None, err
43
+
44
+ return data, None
@@ -0,0 +1,16 @@
1
+ import typer
2
+ import webbrowser
3
+
4
+
5
+ def open_browser(url: str) -> bool:
6
+ try:
7
+ opened = webbrowser.open(url)
8
+ if opened:
9
+ typer.echo("已成功打开浏览器.")
10
+ return True
11
+ else:
12
+ typer.echo("Failed to open the browser.")
13
+ return False
14
+ except Exception as e:
15
+ typer.echo(f"An error occurred when open browser: {e}")
16
+ return False
@@ -0,0 +1,123 @@
1
+ import logging
2
+ from typing import List, Tuple, Dict, Any, Optional
3
+
4
+ import typer
5
+
6
+ from bayes.client import job_run_client, gear_client
7
+ from bayes.client.base import BayesGQLClient
8
+ from bayes.error import Error
9
+ from bayes.model.file.openbayes_yaml import OpenBayesYaml
10
+ from bayes.model.file.settings import BayesEnvConfig, BayesSettings, TypeHyperTuning, DefaultJobModeWorkSpace, \
11
+ DefaultBatchJobModeWorkSpace, DefaultJobModeTask, DefaultBatchJobModeTask
12
+ from bayes.model.party import ModeEnum
13
+ from bayes.usercases import gear_usecase
14
+
15
+
16
+ def create(party_name, directory, project_id, mode, yaml: OpenBayesYaml, datasets: List[str], node_count: int, runtime,
17
+ resource, code_id, command, message) -> Tuple[str, str, Exception]:
18
+ if not project_id:
19
+ project_id, _, project_name, err = gear_usecase.read(directory)
20
+ if err:
21
+ logging.error("Error reading project ID: %s", err)
22
+ return "", "", err
23
+
24
+ if len(datasets) == 0 and yaml is not None:
25
+ datasets = yaml.get_dataset_bindings()
26
+ print(f"yaml.get_dataset_bindings(): {datasets}")
27
+
28
+ if runtime == "" and yaml is not None:
29
+ runtime = yaml.get_runtime()
30
+
31
+ if resource == "" and yaml is not None:
32
+ resource = yaml.resource
33
+
34
+ if command == "" and yaml is not None:
35
+ command = yaml.command
36
+
37
+ if node_count <= 0 and yaml is not None and yaml.node >= 1:
38
+ node_count = yaml.node
39
+ if node_count < 1:
40
+ node_count = 1
41
+
42
+ parameters: Dict[str, Any] = {}
43
+ if yaml is not None and yaml.parameters is not None:
44
+ parameters = yaml.parameters
45
+
46
+ default_env: Optional[BayesEnvConfig] = BayesSettings().default_env
47
+ gql_client = BayesGQLClient(default_env.graphQL, default_env.token)
48
+
49
+ frontend_value = ""
50
+ job_id = ""
51
+ # 如果是hypertuning
52
+ if mode == ModeEnum.hypertuning:
53
+ try:
54
+ hypertuning = job_run_client.create_hypertuning(party_name, gql_client, project_id, datasets, runtime,
55
+ resource,
56
+ code_id, command, yaml.hyper_tuning)
57
+ frontend_value = hypertuning.get_link_value("frontend")
58
+ job_id = hypertuning.id
59
+ except Exception as e:
60
+ return "", "", e
61
+ else:
62
+ try:
63
+ job = job_run_client.create(party_name, gql_client, mode, project_id, datasets, runtime, resource, code_id,
64
+ command, parameters, node_count)
65
+
66
+ frontend_value = job.get_link_value("frontend")
67
+ job_id = job.id
68
+ except Exception as e:
69
+ return "", "", e
70
+
71
+ if message != "":
72
+ err = job_run_client.update_job_description(gql_client, party_name, job_id, message)
73
+ if err is not None:
74
+ return frontend_value, job_id, err
75
+
76
+ return frontend_value, job_id, None
77
+
78
+
79
+ def restart(jid, party_name, data, runtime, resource, task_command, nodeCount, message):
80
+ default_env: Optional[BayesEnvConfig] = BayesSettings().default_env
81
+ gql_client = BayesGQLClient(default_env.graphQL, default_env.token)
82
+
83
+ job = gear_client.get_job_by_id(gql_client, jid, party_name)
84
+ if job is None:
85
+ return None, f"job {jid} not found"
86
+
87
+ mode = job.mode
88
+
89
+ if len(data) == 0:
90
+ data = job.get_job_dataBindings()
91
+
92
+ if runtime == "":
93
+ runtime = job.get_runtime()
94
+ if resource == "":
95
+ resource = job.resource.name
96
+
97
+ frontend_value = ""
98
+ job_id = ""
99
+ if mode == DefaultJobModeWorkSpace:
100
+ job = job_run_client.restart_workspace(gql_client, party_name, jid, runtime, resource, data, [])
101
+ frontend_value = job.get_link_value("frontend")
102
+ job_id = job.id
103
+ elif mode == DefaultBatchJobModeWorkSpace:
104
+ job = job_run_client.restart_batch_workspace(gql_client, party_name, jid, runtime, resource, data, [])
105
+ frontend_value = job.get_link_value("frontend")
106
+ job_id = job.id
107
+ # task 或者 batch task create
108
+ elif mode == DefaultJobModeTask or DefaultBatchJobModeTask:
109
+ if task_command == "" or task_command is None:
110
+ if job.command == "" or job.command is None:
111
+ print("task command 不能为空")
112
+ raise typer.Exit(code=1)
113
+ else:
114
+ task_command = job.command
115
+
116
+ if nodeCount == -1:
117
+ nodeCount = job.children_count()
118
+
119
+ frontend_value, job_id, _ = create(party_name, "", job.project.id, mode, None, data, nodeCount, runtime, resource, job.sourceCode.id, task_command, message)
120
+
121
+ return frontend_value, job_id
122
+
123
+
@@ -0,0 +1,248 @@
1
+ import logging
2
+ import os
3
+ import base64
4
+ import json
5
+ from typing import Optional, Tuple, Callable
6
+
7
+ import requests
8
+ from tusclient import client
9
+ from tusclient.storage import filestorage
10
+ from rich.progress import (
11
+ Progress,
12
+ TextColumn,
13
+ BarColumn,
14
+ TransferSpeedColumn,
15
+ TimeRemainingColumn,
16
+ )
17
+ from tusclient.exceptions import TusCommunicationError
18
+
19
+ from pydantic import BaseModel
20
+
21
+ from bayes.model.file.settings import BayesSettings
22
+ from bayes.model.file.openbayes_data import (
23
+ OpenBayesData,
24
+ OpenBayesDataSettings,
25
+ OpenBayesDataType,
26
+ DATA_FILE_NAME,
27
+ )
28
+ from bayes.model.file.openbayes_ignore import IGNORE_FILE_NAME, IGNORE_CLEANUPS
29
+ from bayes.usercases import openbayes_data_usecase, archive_usecase
30
+ from bayes.usercases.disk_usecase import IgnoreService, DiskService
31
+ from bayes.utils import Utils
32
+
33
+ UPLOAD_PART_SIZE = 268435456 # -1 or 0 means unlimited
34
+ UPLOAD_CODE_LIMIT = 500 # 500MB
35
+ TUS_STORAGE_FILE = ".tus_storage"
36
+
37
+
38
+ class RequestUploadUrl(BaseModel):
39
+ upload_url: str
40
+ token: str
41
+
42
+
43
+ def upload_request() -> Tuple[Optional[RequestUploadUrl], Optional[Exception]]:
44
+ default_env = BayesSettings().default_env
45
+ url = f"{default_env.endpoint}/api/users/{default_env.username}/jobs/upload-request?protocol=tusd"
46
+ print(f"upload_request url:{url}")
47
+ auth_token = default_env.token
48
+
49
+ try:
50
+ response = requests.post(url, headers={"Authorization": f"Bearer {auth_token}"})
51
+ except requests.RequestException as e:
52
+ return None, e
53
+
54
+ logging.info(response)
55
+
56
+ if response.status_code != 200:
57
+ err = Exception(f"Request failed with status code {response.status_code}")
58
+ return None, err
59
+
60
+ try:
61
+ result = response.json()
62
+ upload_request = RequestUploadUrl(**result)
63
+ return upload_request, None
64
+ except ValueError as e:
65
+ return None, e
66
+
67
+
68
+ def _upload_file(
69
+ file_path: str, upload_url: str, token: str
70
+ ) -> Tuple[bool, Optional[str], Optional[Exception]]:
71
+ try:
72
+ my_client = client.TusClient(
73
+ upload_url, headers={"Authorization": f"Bearer {token}"}
74
+ )
75
+ file_size = os.path.getsize(file_path)
76
+
77
+ storage = filestorage.FileStorage(
78
+ os.path.join(os.path.dirname(file_path), TUS_STORAGE_FILE)
79
+ )
80
+
81
+ filename = os.path.basename(file_path)
82
+ metadata = {"filename": filename}
83
+
84
+ with Progress(
85
+ TextColumn("[progress.description]{task.description}"),
86
+ BarColumn(),
87
+ TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
88
+ TransferSpeedColumn(),
89
+ TimeRemainingColumn(),
90
+ ) as progress:
91
+ task = progress.add_task("Uploading", total=file_size)
92
+
93
+ uploader = my_client.uploader(
94
+ file_path,
95
+ chunk_size=2 * 1024 * 1024,
96
+ store_url=True,
97
+ url_storage=storage,
98
+ upload_checksum=False,
99
+ metadata=metadata,
100
+ )
101
+
102
+ while uploader.offset < file_size:
103
+ uploader.upload_chunk()
104
+ progress.update(task, completed=uploader.offset)
105
+
106
+ print(f"File uploaded successfully: {file_path}")
107
+
108
+ storage_path = os.path.join(os.path.dirname(file_path), TUS_STORAGE_FILE)
109
+ if os.path.exists(storage_path):
110
+ os.remove(storage_path)
111
+ print(f"Removed filestorage: {storage_path}")
112
+
113
+ payload_part = token.split(".")[1]
114
+ padded_payload = payload_part + "=" * (4 - len(payload_part) % 4)
115
+ decoded_payload = base64.urlsafe_b64decode(padded_payload).decode("utf-8")
116
+ payload_data = json.loads(decoded_payload)
117
+ sub_payload = json.loads(payload_data["sub"])["payload"]
118
+
119
+ return True, sub_payload, None
120
+
121
+ except TusCommunicationError as e:
122
+ print(f"TUS Communication Error: {e}")
123
+ print(f"Response status: {e.status_code}")
124
+ print(f"Response body: {e.response_content}")
125
+ return False, None, e
126
+ except Exception as e:
127
+ print(f"Unexpected error: {e}")
128
+ return False, None, e
129
+
130
+
131
+ def upload_file(
132
+ file_path: str, upload_url: str, token: str
133
+ ) -> Tuple[bool, Optional[str], Optional[Exception]]:
134
+ return _upload_file(file_path, upload_url, token)
135
+
136
+
137
+ def remove_after_upload_success(zip_path, data_file_path, pid):
138
+ logging.debug("remove after upload success")
139
+ if os.path.exists(zip_path):
140
+ os.remove(zip_path)
141
+ openbayes_data_usecase.remove_by_cur_user(data_file_path, pid)
142
+
143
+
144
+ def has_last_upload(path: str, pid: str) -> Tuple[bool, Optional[OpenBayesData]]:
145
+ data_settings = OpenBayesDataSettings(path)
146
+ data, err = data_settings.read_by_cur_user(pid)
147
+ if data is None:
148
+ return False, None
149
+
150
+ print(f"has_last_upload data.has_last_upload():{data.has_last_upload()}")
151
+
152
+ if data.has_last_upload():
153
+ # Check if the zip file still exists
154
+ if os.path.exists(data.zip):
155
+ # Check if the upload URL is still valid
156
+ print(f"is_upload_url_valid(data.location, data.token):{is_upload_url_valid(data.location, data.token)}")
157
+ if is_upload_url_valid(data.location, data.token):
158
+ return True, data
159
+
160
+ return False, None
161
+
162
+
163
+ def is_upload_url_valid(upload_url: str, token: str) -> bool:
164
+ try:
165
+ headers = {"Authorization": f"Bearer {token}"}
166
+ response = requests.head(upload_url, headers=headers)
167
+ print(f"is_upload_url_valid response.status_code:{response.status_code}")
168
+ if response.status_code == 401:
169
+ print("Token has expired")
170
+ return False
171
+ return True
172
+ except requests.RequestException as e:
173
+ print(f"is_upload_url_valid error: {e}")
174
+ return False
175
+
176
+
177
+ # Update the upload function to support resuming
178
+ def upload(data: OpenBayesData):
179
+ success, payload, err = _upload_file(data.zip, data.location, data.token)
180
+ if success:
181
+ remove_after_upload_success(data.zip, data.path, data.pid)
182
+ return data.length if success else 0, payload, err
183
+
184
+
185
+ def clear_last_upload(data: OpenBayesData):
186
+ if data:
187
+ try:
188
+ os.remove(data.zip)
189
+ except FileNotFoundError:
190
+ print(f"zip_path:{data.zip} not found")
191
+
192
+ print(f"OpenBayesDataSettings(data.path):{OpenBayesDataSettings(data.path)}")
193
+ settings = OpenBayesDataSettings(data.path)
194
+ settings.remove_by_cur_user(data.pid or data.did)
195
+
196
+
197
+ def pre_upload(
198
+ path: str, pid: str, process: Callable
199
+ ) -> Tuple[Optional[dict], Optional[Exception]]:
200
+ process("正在向服务器发送上传请求...")
201
+
202
+ req, err = upload_request()
203
+ if err is not None:
204
+ return None, err
205
+
206
+ process("服务器已响应")
207
+ process("正在读取文件列表,请稍候...")
208
+
209
+ ignore_service = IgnoreService(IGNORE_FILE_NAME, IGNORE_CLEANUPS)
210
+ disk_service = DiskService(ignore_service)
211
+ files, _, err = disk_service.directory_computing(path, UPLOAD_CODE_LIMIT)
212
+ if err is not None:
213
+ return None, err
214
+
215
+ process("剔除在 .openbayesignore 中忽略的文件及文件夹...")
216
+ process(f"共有文件 {files} 个")
217
+
218
+ data_file_path, err = openbayes_data_usecase.write_file(
219
+ path, OpenBayesDataType.CODE, pid
220
+ )
221
+ if err is not None:
222
+ return None, err
223
+
224
+ process("正在压缩代码...")
225
+
226
+ zip_path = Utils.generate_temp_zip_path()
227
+ err = archive_usecase.archive(path, zip_path)
228
+ if err is not None:
229
+ return None, err
230
+
231
+ process("压缩代码完成")
232
+
233
+ try:
234
+ stat = os.stat(zip_path)
235
+ except Exception as e:
236
+ return None, e
237
+
238
+ process("正在初始化上传中...")
239
+ process(f"正在上传压缩包。总共上传大小:{Utils.byte_size(stat.st_size, True)}")
240
+
241
+ data, err = openbayes_data_usecase.update_by_cur_user(
242
+ data_file_path, pid, req.upload_url, req.token, zip_path, stat.st_size
243
+ )
244
+
245
+ if err is not None:
246
+ return None, err
247
+
248
+ return data, None