openbayes-cli 0.28.2__tar.gz → 0.28.3__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.
Files changed (72) hide show
  1. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/PKG-INFO +1 -1
  2. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/gear_client.py +16 -1
  3. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/job_run_client.py +39 -25
  4. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/commands/gear.py +33 -25
  5. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/commands/hpc.py +32 -23
  6. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/templates/openbayes_zh-Hans.yaml +12 -5
  7. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/gear_run_usecase.py +30 -25
  8. openbayes_cli-0.28.3/bayes/usercases/secret_parameter_usecase.py +110 -0
  9. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/pyproject.toml +1 -1
  10. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/README.md +0 -0
  11. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/__init__.py +0 -0
  12. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/__init__.py +0 -0
  13. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/base.py +0 -0
  14. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/dataset_client.py +0 -0
  15. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/dataset_list_client.py +0 -0
  16. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/dataset_upload_client.py +0 -0
  17. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/dataset_version_client.py +0 -0
  18. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/minio_storage_client.py +0 -0
  19. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/org_client.py +0 -0
  20. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/resource_client.py +0 -0
  21. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/runtime_client.py +0 -0
  22. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/ssh_client.py +0 -0
  23. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/status_client.py +0 -0
  24. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/client/user_client.py +0 -0
  25. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/commands/__init__.py +0 -0
  26. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/commands/data.py +0 -0
  27. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/commands/org.py +0 -0
  28. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/commands/ssh.py +0 -0
  29. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/error.py +0 -0
  30. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/__init__.py +0 -0
  31. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/dataset.py +0 -0
  32. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/dataset_version.py +0 -0
  33. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/file/__init__.py +0 -0
  34. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/file/bayes_file.py +0 -0
  35. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/file/data_bindings.py +0 -0
  36. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/file/openbayes_gear.py +0 -0
  37. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/file/openbayes_ignore.py +0 -0
  38. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/file/openbayes_yaml.py +0 -0
  39. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/file/settings.py +0 -0
  40. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/party.py +0 -0
  41. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/resource.py +0 -0
  42. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/model/runtime.py +0 -0
  43. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/root.py +0 -0
  44. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/templates/__init__.py +0 -0
  45. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/templates/openbayesignore.yaml +0 -0
  46. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/__init__.py +0 -0
  47. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/archive_usecase.py +0 -0
  48. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/auth_usecase.py +0 -0
  49. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/dataset_list_usecase.py +0 -0
  50. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/dataset_usecase.py +0 -0
  51. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/dataset_version_usecase.py +0 -0
  52. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/disk_usecase.py +0 -0
  53. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/gear_download_usecese.py +0 -0
  54. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/gear_logs_usecase.py +0 -0
  55. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/gear_usecase.py +0 -0
  56. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/minio_storage_usecase.py +0 -0
  57. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/open_usecase.py +0 -0
  58. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/org_usecase.py +0 -0
  59. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/resource_usecase.py +0 -0
  60. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/runtime_usecase.py +0 -0
  61. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/ssh_usecase.py +0 -0
  62. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/switch_usercase.py +0 -0
  63. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/usercases/upload_state_manager.py +0 -0
  64. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/utils/__init__.py +0 -0
  65. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/utils/add_global_param.py +0 -0
  66. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/bayes/utils.py +0 -0
  67. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/openbayestool/__init__.py +0 -0
  68. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/openbayestool/config.py +0 -0
  69. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/openbayestool/http_client.py +0 -0
  70. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/openbayestool/jwt_client.py +0 -0
  71. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/openbayestool/tracker.py +0 -0
  72. {openbayes_cli-0.28.2 → openbayes_cli-0.28.3}/openbayestool/updater.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openbayes-cli
3
- Version: 0.28.2
3
+ Version: 0.28.3
4
4
  Summary: A Python CLI for OpenBayes
5
5
  License: OpenBayes Public Source License
6
6
  Keywords: openbayes,cli,machine learning
@@ -1,7 +1,7 @@
1
1
  import json
2
2
  import sys
3
3
  from datetime import datetime
4
- from typing import List, Optional, Union, Tuple
4
+ from typing import Any, List, Optional, Union, Tuple
5
5
 
6
6
  import requests
7
7
  from gql.transport.exceptions import TransportQueryError
@@ -61,6 +61,12 @@ class SourceCode(BaseModel):
61
61
  id: str
62
62
 
63
63
 
64
+ class JobParameter(BaseModel):
65
+ name: str
66
+ value: Optional[Any] = None
67
+ isSecret: bool = False
68
+
69
+
64
70
  class Job(BaseModel):
65
71
  id: Optional[str] = None
66
72
  name: Optional[str] = None
@@ -77,6 +83,8 @@ class Job(BaseModel):
77
83
  dataBindings: Optional[List[Union[DataBinding, DatasetBinding, JobOutputBinding]]] = Field(default_factory=list)
78
84
  sourceCode: Optional[SourceCode] = None
79
85
  command: Optional[str] = None
86
+ rawCommand: Optional[str] = None
87
+ parameters: Optional[List[JobParameter]] = Field(default_factory=list)
80
88
  children: Optional[List['Job']] = Field(default_factory=list)
81
89
  batchParentId: Optional[str] = None
82
90
 
@@ -435,17 +443,24 @@ def get_job_by_id(client: BayesGQLClient, id, party_name):
435
443
  name
436
444
  id
437
445
  }
446
+ parameters {
447
+ name
448
+ value
449
+ isSecret
450
+ }
438
451
  ... on Task {
439
452
  sourceCode {
440
453
  id
441
454
  }
442
455
  command
456
+ rawCommand
443
457
  }
444
458
  ... on BatchTask {
445
459
  sourceCode {
446
460
  id
447
461
  }
448
462
  command
463
+ rawCommand
449
464
  size
450
465
  children {
451
466
  id
@@ -1,9 +1,7 @@
1
1
  import json
2
2
  import sys
3
3
  from enum import Enum
4
- from typing import Tuple, Optional, List
5
-
6
- import pydantic
4
+ from typing import Any, Dict, Tuple, Optional, List
7
5
  import typer
8
6
  from gql.transport.exceptions import TransportQueryError
9
7
  from pydantic import BaseModel, Field
@@ -53,6 +51,12 @@ class TagInput(BaseModel):
53
51
  name: Optional[str] = None
54
52
 
55
53
 
54
+ class JobParameterInput(BaseModel):
55
+ name: str
56
+ value: Optional[Any] = None
57
+ isSecret: Optional[bool] = False
58
+
59
+
56
60
  class VariableInput(BaseModel):
57
61
  mode: str
58
62
  projectId: str
@@ -63,6 +67,7 @@ class VariableInput(BaseModel):
63
67
  newWorkspace: Optional[NewWorkspaceInput] = None
64
68
  newBatchWorkspace: Optional[BatchWorkSpace] = None
65
69
  dataBindings: Optional[List[DataBinding]] = None
70
+ parameters: Optional[List[JobParameterInput]] = None
66
71
  tagNames: Optional[List[TagInput]] = None
67
72
 
68
73
 
@@ -115,14 +120,30 @@ class CreateHypertuningInput(BaseModel):
115
120
  goal: str
116
121
  algorithm: str
117
122
  parameterSpecs: List[ParameterSpecInput]
123
+ parameters: Optional[List[JobParameterInput]] = None
118
124
 
119
125
 
120
- def build_command(command: str, parameters: dict) -> str:
126
+ def build_job_parameters(parameters: Optional[Dict[str, Any]]) -> List[JobParameterInput]:
121
127
  if not parameters:
122
- return command
123
- else:
124
- param_str = " ".join([f"--{k}={v}" for k, v in parameters.items()])
125
- return f"{command} {param_str}"
128
+ return []
129
+
130
+ result: List[JobParameterInput] = []
131
+ for name, raw_value in parameters.items():
132
+ if isinstance(raw_value, dict) and (
133
+ "value" in raw_value or "isSecret" in raw_value or "is_secret" in raw_value
134
+ ):
135
+ result.append(
136
+ JobParameterInput(
137
+ name=name,
138
+ value=raw_value.get("value"),
139
+ isSecret=bool(raw_value.get("is_secret", raw_value.get("isSecret", False))),
140
+ )
141
+ )
142
+ continue
143
+
144
+ result.append(JobParameterInput(name=name, value=raw_value, isSecret=False))
145
+
146
+ return result
126
147
 
127
148
 
128
149
  def get_graphql_binding_datasets(datasets: List[str]) -> List[DataBinding]:
@@ -164,6 +185,7 @@ def create(party_name, client: BayesGQLClient, mode, project_id, datasets: List[
164
185
  resource=resource
165
186
  )
166
187
  input_data.tagNames = [TagInput(name="BUSINESS_CHANNEL_ML")]
188
+ input_data.parameters = build_job_parameters(parameters)
167
189
 
168
190
  upper_mode = mode.upper()
169
191
  if upper_mode == DefaultJobModeTask or upper_mode == DefaultBatchJobModeTask:
@@ -171,14 +193,14 @@ def create(party_name, client: BayesGQLClient, mode, project_id, datasets: List[
171
193
  input_data.newBatchTask = BatchTask(
172
194
  size=node_count,
173
195
  code=code_id,
174
- command=build_command(command, parameters)
196
+ command=command
175
197
  )
176
198
  input_data.mode = "BATCH_TASK"
177
199
  else:
178
200
  input_data.mode = "TASK"
179
201
  input_data.newTask = NewTaskInput(
180
202
  code=code_id,
181
- command=build_command(command, parameters)
203
+ command=command
182
204
  )
183
205
  elif upper_mode == DefaultJobModeWorkSpace or upper_mode == DefaultBatchJobModeWorkSpace:
184
206
  if node_count > 1:
@@ -289,11 +311,6 @@ class DataBindingInput(BaseModel):
289
311
  bindingAuth: Optional[BindingAuth] = None
290
312
 
291
313
 
292
- class JobParameterInput(BaseModel):
293
- name: str
294
- value: pydantic.Json
295
-
296
-
297
314
  class RestartWorkspaceInput(BaseModel):
298
315
  dataBindings: List[DataBindingInput]
299
316
  runtime: str
@@ -312,7 +329,8 @@ class RestartBatchWorkspaceInput(BaseModel):
312
329
 
313
330
 
314
331
  def create_hypertuning(party_name, client: BayesGQLClient, project_id, datasets, runtime, resource, code_id,
315
- command, hyper_tuning: HyperTuning):
332
+ command, hyper_tuning: HyperTuning,
333
+ parameters: Optional[Dict[str, Any]] = None):
316
334
  # 判断hyper_tuning的参数,不为空字符串和None
317
335
  if Utils.is_empty_or_none(hyper_tuning.hyperparameter_metric) or Utils.is_empty_or_none(
318
336
  hyper_tuning.algorithm) or Utils.is_empty_or_none(hyper_tuning.goal):
@@ -344,7 +362,8 @@ def create_hypertuning(party_name, client: BayesGQLClient, project_id, datasets,
344
362
  algorithm=hyper_tuning_algorithm,
345
363
  parameterSpecs=parameter_specs_input,
346
364
  sideMetrics=hyper_tuning.side_metrics,
347
- parallelCount=max(DEFAULT_PARALLEL_COUNT, hyper_tuning.parallel_count)
365
+ parallelCount=max(DEFAULT_PARALLEL_COUNT, hyper_tuning.parallel_count),
366
+ parameters=build_job_parameters(parameters) or None,
348
367
  )
349
368
 
350
369
  if datasets and len(datasets) > 0:
@@ -436,20 +455,15 @@ def get_restart_job_graphql_dataBindings(dataBindings: List[str]) -> DataBinding
436
455
  if len(list_parts) == 2:
437
456
  name = list_parts[0]
438
457
  path = list_parts[1]
439
-
440
- binding_type = "OUTPUT" if name.endswith("/output") else None
458
+ # bindingType 由服务器根据 name 是否以 "output" 结尾自动推导,无需客户端传递
441
459
  result.append(DataBindingInput(
442
460
  name=name,
443
461
  path=path,
444
462
  bindingAuth=BindingAuthType.READ_ONLY,
445
- bindingType=binding_type
446
463
  ))
447
464
  elif len(list_parts) == 3:
448
465
  name = list_parts[0]
449
466
  path = list_parts[1]
450
-
451
- binding_type = "OUTPUT" if name.endswith("/output") else None
452
-
453
467
  auth = list_parts[2].upper()
454
468
  if auth == "RO" or auth == "READ_ONLY":
455
469
  result.append(DataBindingInput(
@@ -488,7 +502,7 @@ def restart_workspace(client: BayesGQLClient, party_name, jid, runtime, resource
488
502
  dataBindings=get_restart_job_graphql_dataBindings(dataBindings) if dataBindings else []
489
503
  )
490
504
 
491
- variables = {"userId": party_name, "jobId": jid, "input": input_data.model_dump()}
505
+ variables = {"userId": party_name, "jobId": jid, "input": input_data.model_dump(exclude_none=True)}
492
506
 
493
507
  try:
494
508
  response = client.exec(query, variables)
@@ -538,7 +552,7 @@ def restart_batch_workspace(client: BayesGQLClient, party_name, jid, runtime, re
538
552
  dataBindings=get_restart_job_graphql_dataBindings(dataBindings) if dataBindings else []
539
553
  )
540
554
 
541
- variables = {"userId": party_name, "jobId": jid, "input": input_data.model_dump()}
555
+ variables = {"userId": party_name, "jobId": jid, "input": input_data.model_dump(exclude_none=True)}
542
556
 
543
557
  try:
544
558
  response = client.exec(query, variables)
@@ -10,7 +10,7 @@ from bayes.model.file.openbayes_yaml import OpenBayesYamlSettings, FILE_NAME, DE
10
10
  from bayes.model.file.settings import BayesEnvConfig, BayesSettings
11
11
  from bayes.model.party import ModeEnum
12
12
  from bayes.usercases import auth_usecase, gear_usecase, open_usecase, resource_usecase, runtime_usecase, \
13
- gear_logs_usecase, gear_download_usecese, gear_run_usecase, minio_storage_usecase
13
+ gear_logs_usecase, gear_download_usecese, gear_run_usecase, minio_storage_usecase, secret_parameter_usecase
14
14
  from bayes.utils import Utils
15
15
 
16
16
  app = typer.Typer()
@@ -218,22 +218,31 @@ def run(
218
218
  print("准备创建 task,执行命令不能为空")
219
219
  raise typer.Exit(code=1)
220
220
 
221
+ # 在上传源码前校验并解析 secret 参数
222
+ # source: env 写法会在此读取本地环境变量;若环境变量缺失则在此 fail-fast,不浪费上传时间
223
+ raw_parameters = yaml_settings.configuration.parameters if yaml_settings.configuration else {}
224
+ try:
225
+ resolved_parameters = secret_parameter_usecase.resolve_secret_parameters(raw_parameters)
226
+ except ValueError as e:
227
+ print(f"参数校验失败:{e}")
228
+ raise typer.Exit(code=1)
229
+
221
230
  # 根据模式设置 source_code_id
222
231
  if mode == ModeEnum.task or mode == ModeEnum.hypertuning:
223
232
  print("正在上传源代码...")
224
233
  source_code_id = minio_storage_usecase.upload_source_code(party_name, str(current_path), "TEMPORARY")
225
- # source_code_id = minio_storage_usecase.upload_source_code(party_name, "/Users/haohao/test-cli2", "TEMPORARY")
226
234
  if source_code_id is None:
227
235
  print("源代码上传失败,无法继续创建容器")
228
236
  raise typer.Exit(code=1)
229
237
  else:
230
238
  # workspace 模式下设置为空字符串
231
239
  source_code_id = ""
232
-
240
+
233
241
  print("正在向服务器请求创建容器...")
234
242
 
235
243
  frontend_value, job_id, err = gear_run_usecase.create(party_name, current_path, "", mode, yaml_settings.configuration,
236
- data, node, env, resource, source_code_id, task_command, message)
244
+ data, node, env, resource, source_code_id, task_command, message,
245
+ parameters_override=resolved_parameters)
237
246
 
238
247
  if err is not None and frontend_value == "":
239
248
  print(f"create err:{err}")
@@ -309,14 +318,12 @@ def restart(ctx: typer.Context,
309
318
  data: List[str] = typer.Option([], "-d", "--data", help="绑定数据"),
310
319
  env: str = typer.Option("", "-e", "--env", help="选择镜像"),
311
320
  resource: str = typer.Option("", "-r", "--resource", help="选择算力"),
312
- follow: bool = typer.Option(False, "-f", "--follow", help="跟踪容器运行;task 模式会自动打印实时日志直到任务结束,workspace 进入运行后退出"),
321
+ follow: bool = typer.Option(False, "-f", "--follow", help="跟踪容器运行;workspace 进入运行后退出"),
313
322
  message: str = typer.Option("", "-m", "--message", help="执行描述"),
314
- open_browser: bool = typer.Option(False, "-o", "--open", help="成功创建容器后,在浏览器打开"),
315
- node: int = typer.Option(-1, "-n", "--node", help="指定运行节点数量"),
316
- extra_args: Optional[List[str]] = typer.Argument(None, help="task command 额外参数")
323
+ open_browser: bool = typer.Option(False, "-o", "--open", help="成功重启后,在浏览器打开"),
317
324
  ):
318
325
  """
319
- 继续执行容器
326
+ 重启 workspace 容器(仅支持 workspace / batch_workspace)
320
327
 
321
328
  用法:
322
329
  bayes gear restart [任务编码] [选项]
@@ -327,16 +334,20 @@ def restart(ctx: typer.Context,
327
334
 
328
335
  -e, --env string 选择镜像
329
336
 
330
- -f, --follow [可选] 跟踪容器运行;task 模式会自动打印实时日志直到任务结束,workspace 进入运行后退出
337
+ -f, --follow [可选] 跟踪容器运行;workspace 进入运行后退出
331
338
 
332
339
  -h, --help 查看 restart 的帮助
333
340
 
334
341
  -m, --message string 执行描述
335
342
 
336
- -o, --open [可选] 继续执行容器后,在浏览器打开
343
+ -o, --open [可选] 重启后,在浏览器打开
337
344
 
338
345
  -r, --resource string 选择算力
339
346
 
347
+ 注意:
348
+ restart 仅支持 workspace / batch_workspace 类型。
349
+ 如需重新执行 task,请在项目目录使用 `bayes gear run task`。
350
+
340
351
  """
341
352
  # 检查用户是否已登录
342
353
  login = auth_usecase.check_login()
@@ -360,17 +371,17 @@ def restart(ctx: typer.Context,
360
371
  print("请输入正确的任务编码")
361
372
  raise typer.Exit(code=1)
362
373
 
374
+ if not job.is_workspace():
375
+ print("错误:restart 仅支持 workspace / batch_workspace,不支持 task / batch_task。")
376
+ print("如需重新执行 task,请在项目目录使用 `bayes gear run task`。")
377
+ raise typer.Exit(code=1)
378
+
363
379
  project_name = job.project.name
364
380
  frontend_url = job.get_link_value("frontend")
365
381
  open_url = Utils.replace_last_id(frontend_url, job.id)
366
382
 
367
- task_command = ""
368
- if extra_args:
369
- task_command = " ".join(extra_args)
370
-
371
383
  try:
372
- new_frontend_value, job_id = gear_run_usecase.restart(id, party_name, data, env, resource, task_command, node,
373
- message)
384
+ new_frontend_value, job_id = gear_run_usecase.restart(id, party_name, data, env, resource)
374
385
  except Exception as e:
375
386
  print(f"e:{e}")
376
387
  print("重启失败")
@@ -378,22 +389,19 @@ def restart(ctx: typer.Context,
378
389
  print(f"打开网页 {open_url} 可查看容器 {project_name} 的详细信息")
379
390
  raise typer.Exit(code=1)
380
391
 
381
- print("容器继续执行...")
382
- if new_frontend_value is not None:
383
- new_open_url = Utils.replace_last_id(new_frontend_value, job_id)
392
+ print("容器重启中...")
393
+ new_open_url = Utils.replace_last_id(new_frontend_value, job_id) if new_frontend_value else None
394
+ if new_open_url:
384
395
  print(f"打开网页 {new_open_url} 可查看容器 {project_name} 的详细信息")
385
396
 
386
397
  if message != "":
387
398
  gear_usecase.update_job_description(party_name, job_id, message)
388
399
 
389
- if open_browser:
400
+ if open_browser and new_open_url:
390
401
  open_usecase.open_browser(new_open_url)
391
402
 
392
403
  if follow:
393
- if job.is_workspace():
394
- gear_usecase.follow_status(job_id, party_name, stop_with_running=True)
395
- else:
396
- gear_usecase.follow_logs_until_finished(job_id, party_name)
404
+ gear_usecase.follow_status(job_id, party_name, stop_with_running=True)
397
405
  gear_usecase.print_last_status(job_id, party_name)
398
406
 
399
407
 
@@ -10,7 +10,7 @@ from bayes.model.file.openbayes_yaml import OpenBayesYamlSettings, FILE_NAME, DE
10
10
  from bayes.model.file.settings import BayesEnvConfig, BayesSettings
11
11
  from bayes.model.party import ModeEnum
12
12
  from bayes.usercases import auth_usecase, gear_usecase, open_usecase, resource_usecase, runtime_usecase, \
13
- gear_logs_usecase, gear_download_usecese, gear_run_usecase, minio_storage_usecase
13
+ gear_logs_usecase, gear_download_usecese, gear_run_usecase, minio_storage_usecase, secret_parameter_usecase
14
14
  from bayes.utils import Utils
15
15
 
16
16
  app = typer.Typer()
@@ -219,6 +219,15 @@ def run(
219
219
  print("准备创建 task,执行命令不能为空")
220
220
  raise typer.Exit(code=1)
221
221
 
222
+ # 在上传源码前校验并解析 secret 参数
223
+ # source: env 写法会在此读取本地环境变量;若环境变量缺失则在此 fail-fast,不浪费上传时间
224
+ raw_parameters = yaml_settings.configuration.parameters if yaml_settings.configuration else {}
225
+ try:
226
+ resolved_parameters = secret_parameter_usecase.resolve_secret_parameters(raw_parameters)
227
+ except ValueError as e:
228
+ print(f"参数校验失败:{e}")
229
+ raise typer.Exit(code=1)
230
+
222
231
  # 根据模式设置 source_code_id
223
232
  if mode == ModeEnum.task or mode == ModeEnum.hypertuning:
224
233
  print("正在上传源代码...")
@@ -236,7 +245,8 @@ def run(
236
245
  frontend_value, job_id, err = gear_run_usecase.create(party_name, current_path, "", mode,
237
246
  yaml_settings.configuration,
238
247
  data, node, env, resource, source_code_id, task_command,
239
- message)
248
+ message,
249
+ parameters_override=resolved_parameters)
240
250
 
241
251
  if err is not None and frontend_value == "":
242
252
  print(f"create err:{err}")
@@ -310,14 +320,12 @@ def restart(ctx: typer.Context,
310
320
  data: List[str] = typer.Option([], "-d", "--data", help="绑定数据"),
311
321
  env: str = typer.Option("", "-e", "--env", help="选择镜像"),
312
322
  resource: str = typer.Option("", "-r", "--resource", help="选择算力"),
313
- follow: bool = typer.Option(False, "-f", "--follow", help="跟踪容器运行;task 模式会自动打印实时日志直到任务结束,workspace 进入运行后退出"),
323
+ follow: bool = typer.Option(False, "-f", "--follow", help="跟踪容器运行;workspace 进入运行后退出"),
314
324
  message: str = typer.Option("", "-m", "--message", help="执行描述"),
315
- open_browser: bool = typer.Option(False, "-o", "--open", help="成功创建容器后,在浏览器打开"),
316
- node: int = typer.Option(-1, "-n", "--node", help="指定运行节点数量"),
317
- extra_args: Optional[List[str]] = typer.Argument(None, help="task command 额外参数")
325
+ open_browser: bool = typer.Option(False, "-o", "--open", help="成功重启后,在浏览器打开"),
318
326
  ):
319
327
  """
320
- 继续执行容器
328
+ 重启 workspace 容器(仅支持 workspace / batch_workspace)
321
329
 
322
330
  用法:
323
331
  bayes hpc restart [任务编码] [选项]
@@ -328,16 +336,20 @@ def restart(ctx: typer.Context,
328
336
 
329
337
  -e, --env string 选择镜像
330
338
 
331
- -f, --follow [可选] 跟踪容器运行;task 模式会自动打印实时日志直到任务结束,workspace 进入运行后退出
339
+ -f, --follow [可选] 跟踪容器运行;workspace 进入运行后退出
332
340
 
333
341
  -h, --help 查看 restart 的帮助
334
342
 
335
343
  -m, --message string 执行描述
336
344
 
337
- -o, --open [可选] 继续执行容器后,在浏览器打开
345
+ -o, --open [可选] 重启后,在浏览器打开
338
346
 
339
347
  -r, --resource string 选择算力
340
348
 
349
+ 注意:
350
+ restart 仅支持 workspace / batch_workspace 类型。
351
+ 如需重新执行 task,请在项目目录使用 `bayes hpc run task`。
352
+
341
353
  """
342
354
  # 检查用户是否已登录
343
355
  login = auth_usecase.check_login()
@@ -361,17 +373,17 @@ def restart(ctx: typer.Context,
361
373
  print("请输入正确的任务编码")
362
374
  raise typer.Exit(code=1)
363
375
 
376
+ if not job.is_workspace():
377
+ print("错误:restart 仅支持 workspace / batch_workspace,不支持 task / batch_task。")
378
+ print("如需重新执行 task,请在项目目录使用 `bayes hpc run task`。")
379
+ raise typer.Exit(code=1)
380
+
364
381
  project_name = job.project.name
365
382
  frontend_url = job.get_link_value("frontend")
366
383
  open_url = Utils.replace_last_id(frontend_url, job.id)
367
384
 
368
- task_command = ""
369
- if extra_args:
370
- task_command = " ".join(extra_args)
371
-
372
385
  try:
373
- new_frontend_value, job_id = gear_run_usecase.restart(id, party_name, data, env, resource, task_command, node,
374
- message)
386
+ new_frontend_value, job_id = gear_run_usecase.restart(id, party_name, data, env, resource)
375
387
  except Exception as e:
376
388
  print(f"e:{e}")
377
389
  print("重启失败")
@@ -379,22 +391,19 @@ def restart(ctx: typer.Context,
379
391
  print(f"打开网页 {open_url} 可查看容器 {project_name} 的详细信息")
380
392
  raise typer.Exit(code=1)
381
393
 
382
- print("容器继续执行...")
383
- if new_frontend_value is not None:
384
- new_open_url = Utils.replace_last_id(new_frontend_value, job_id)
394
+ print("容器重启中...")
395
+ new_open_url = Utils.replace_last_id(new_frontend_value, job_id) if new_frontend_value else None
396
+ if new_open_url:
385
397
  print(f"打开网页 {new_open_url} 可查看容器 {project_name} 的详细信息")
386
398
 
387
399
  if message != "":
388
400
  gear_usecase.update_job_description(party_name, job_id, message)
389
401
 
390
- if open_browser:
402
+ if open_browser and new_open_url:
391
403
  open_usecase.open_browser(new_open_url)
392
404
 
393
405
  if follow:
394
- if job.is_workspace():
395
- gear_usecase.follow_status(job_id, party_name, stop_with_running=True)
396
- else:
397
- gear_usecase.follow_logs_until_finished(job_id, party_name)
406
+ gear_usecase.follow_status(job_id, party_name, stop_with_running=True)
398
407
  gear_usecase.print_last_status(job_id, party_name)
399
408
 
400
409
 
@@ -43,15 +43,22 @@ command: ""
43
43
  node: 1
44
44
 
45
45
  ## parameters
46
- # 支持 key / value 形式的参数,该参数会在容器执行时生成 openbayes_params.json 并补充在 command 参数后面
47
- # 样例如下:
46
+ # 支持 key / value 形式的参数,会通过 createJob / restartJob 的 GraphQL `parameters` 字段提交给服务端。
47
+ # 对于 task,非敏感参数会由服务端追加到 command 后面;敏感参数只会作为环境变量注入,不会出现在 command 中。
48
+ #
49
+ # secret 参数支持通过本地环境变量注入,避免明文出现在文件里:
48
50
  #
49
51
  # parameters:
50
- # input: /input0
51
52
  # epochs: 5
53
+ # HF_TOKEN:
54
+ # is_secret: true
55
+ # source: env # 从本地环境变量读取
56
+ # env: HF_TOKEN # 对应的环境变量名
57
+ #
58
+ # 运行前先在终端执行:export HF_TOKEN=hf_xxx
59
+ # CLI 会在上传源码前读取该变量,若变量不存在则立即报错退出。
52
60
  #
53
- # 在执行时会生成一个内容为 {"input": "/input0", "epochs": 5} openbayes_params.json,
54
- # 并且会在执行命令后面追加 `--input=/input0 --epochs=5`
61
+ # workspace / batch workspace 重启时,CLI 会自动保留已有敏感参数。
55
62
  #
56
63
  parameters: {}
57
64
 
@@ -1,20 +1,32 @@
1
1
  import logging
2
2
  from typing import List, Tuple, Dict, Any, Optional
3
3
 
4
- import typer
5
-
6
4
  from bayes.client import job_run_client, gear_client
7
5
  from bayes.client.base import BayesGQLClient
8
6
  from bayes.error import Error
9
7
  from bayes.model.file.openbayes_yaml import OpenBayesYaml
10
8
  from bayes.model.file.settings import BayesEnvConfig, BayesSettings, TypeHyperTuning, DefaultJobModeWorkSpace, \
11
- DefaultBatchJobModeWorkSpace, DefaultJobModeTask, DefaultBatchJobModeTask
9
+ DefaultBatchJobModeWorkSpace
12
10
  from bayes.model.party import ModeEnum
13
11
  from bayes.usercases import gear_usecase
14
12
 
15
13
 
14
+ def _build_restart_parameters(job: gear_client.Job) -> List[job_run_client.JobParameterInput]:
15
+ result: List[job_run_client.JobParameterInput] = []
16
+ for parameter in job.parameters or []:
17
+ result.append(
18
+ job_run_client.JobParameterInput(
19
+ name=parameter.name,
20
+ value=None if parameter.isSecret else parameter.value,
21
+ isSecret=parameter.isSecret,
22
+ )
23
+ )
24
+ return result
25
+
26
+
16
27
  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]:
28
+ resource, code_id, command, message,
29
+ parameters_override: Optional[Dict[str, Any]] = None) -> Tuple[str, str, Exception]:
18
30
  if not project_id:
19
31
  project_id, _, project_name, err = gear_usecase.read(directory)
20
32
  if err:
@@ -40,7 +52,9 @@ def create(party_name, directory, project_id, mode, yaml: OpenBayesYaml, dataset
40
52
  node_count = 1
41
53
 
42
54
  parameters: Dict[str, Any] = {}
43
- if yaml is not None and yaml.parameters is not None:
55
+ if parameters_override is not None:
56
+ parameters = parameters_override
57
+ elif yaml is not None and yaml.parameters is not None:
44
58
  parameters = yaml.parameters
45
59
 
46
60
  if code_id is None and (mode == ModeEnum.task or mode == ModeEnum.hypertuning):
@@ -56,7 +70,8 @@ def create(party_name, directory, project_id, mode, yaml: OpenBayesYaml, dataset
56
70
  try:
57
71
  hypertuning = job_run_client.create_hypertuning(party_name, gql_client, project_id, datasets, runtime,
58
72
  resource,
59
- code_id, command, yaml.hyper_tuning)
73
+ code_id, command, yaml.hyper_tuning,
74
+ parameters=parameters)
60
75
  frontend_value = hypertuning.get_link_value("frontend")
61
76
  job_id = hypertuning.id
62
77
  except Exception as e:
@@ -79,13 +94,13 @@ def create(party_name, directory, project_id, mode, yaml: OpenBayesYaml, dataset
79
94
  return frontend_value, job_id, None
80
95
 
81
96
 
82
- def restart(jid, party_name, data, runtime, resource, task_command, nodeCount, message):
97
+ def restart(jid, party_name, data, runtime, resource) -> Tuple[str, str]:
83
98
  default_env: Optional[BayesEnvConfig] = BayesSettings().default_env
84
99
  gql_client = BayesGQLClient(default_env.graphQL, default_env.token)
85
100
 
86
101
  job = gear_client.get_job_by_id(gql_client, jid, party_name)
87
102
  if job is None:
88
- return None, f"job {jid} not found"
103
+ raise Error(f"job {jid} not found")
89
104
 
90
105
  mode = job.mode
91
106
 
@@ -97,30 +112,20 @@ def restart(jid, party_name, data, runtime, resource, task_command, nodeCount, m
97
112
  if resource == "":
98
113
  resource = job.resource.name
99
114
 
115
+ restart_parameters = _build_restart_parameters(job)
100
116
  frontend_value = ""
101
117
  job_id = ""
102
118
  if mode == DefaultJobModeWorkSpace:
103
- job = job_run_client.restart_workspace(gql_client, party_name, jid, runtime, resource, data, [])
119
+ job = job_run_client.restart_workspace(
120
+ gql_client, party_name, jid, runtime, resource, data, restart_parameters
121
+ )
104
122
  frontend_value = job.get_link_value("frontend")
105
123
  job_id = job.id
106
124
  elif mode == DefaultBatchJobModeWorkSpace:
107
- job = job_run_client.restart_batch_workspace(gql_client, party_name, jid, runtime, resource, data, [])
125
+ job = job_run_client.restart_batch_workspace(
126
+ gql_client, party_name, jid, runtime, resource, data, restart_parameters
127
+ )
108
128
  frontend_value = job.get_link_value("frontend")
109
129
  job_id = job.id
110
- # task 或者 batch task create
111
- elif mode == DefaultJobModeTask or DefaultBatchJobModeTask:
112
- if task_command == "" or task_command is None:
113
- if job.command == "" or job.command is None:
114
- print("task command 不能为空")
115
- raise typer.Exit(code=1)
116
- else:
117
- task_command = job.command
118
-
119
- if nodeCount == -1:
120
- nodeCount = job.children_count()
121
-
122
- frontend_value, job_id, _ = create(party_name, "", job.project.id, mode, None, data, nodeCount, runtime, resource, job.sourceCode.id, task_command, message)
123
130
 
124
131
  return frontend_value, job_id
125
-
126
-
@@ -0,0 +1,110 @@
1
+ import os
2
+ from typing import Dict, Any, Optional
3
+
4
+ import typer
5
+
6
+
7
+ def resolve_secret_parameters(parameters: Optional[Dict[str, Any]]) -> Dict[str, Any]:
8
+ """
9
+ 在源码上传前解析 openbayes.yaml 的 parameters 字段。
10
+
11
+ 处理两类 secret 写法:
12
+ - 推荐写法:is_secret: true + source: env + env: VAR_NAME
13
+ → 从本地环境变量读取真实值,替换成 {value: <真实值>, is_secret: true}
14
+ → 若环境变量不存在或为空,立即抛出 ValueError(调用方应在上传前 fail-fast)
15
+ - 兼容写法:is_secret: true + value: <明文>
16
+ → 打印迁移提示,只含参数名,不含值,放行继续
17
+ - 非法组合(source + value 同时存在、非 secret 参数带 source 等)
18
+ → 立即抛出 ValueError
19
+
20
+ 非 secret 的标量参数不做任何处理,直接透传。
21
+
22
+ Args:
23
+ parameters: openbayes.yaml 中解析出的 parameters 字典,可为 None
24
+
25
+ Returns:
26
+ 解析后的参数字典,source: env 已替换为真实值
27
+
28
+ Raises:
29
+ ValueError: YAML 配置非法,或环境变量缺失/为空
30
+ """
31
+ if not parameters:
32
+ return parameters or {}
33
+
34
+ resolved: Dict[str, Any] = {}
35
+
36
+ for name, raw_value in parameters.items():
37
+ # 非 dict 的标量参数(普通非敏感参数),直接透传
38
+ if not isinstance(raw_value, dict):
39
+ resolved[name] = raw_value
40
+ continue
41
+
42
+ is_secret = bool(raw_value.get("is_secret", raw_value.get("isSecret", False)))
43
+ source = raw_value.get("source")
44
+ value = raw_value.get("value")
45
+ env_var_name = raw_value.get("env")
46
+
47
+ # 校验:source 只允许出现在 is_secret=true 的参数上
48
+ if source is not None and not is_secret:
49
+ raise ValueError(
50
+ f"参数 '{name}' 配置了 source 但 is_secret 不为 true,这是非法配置,"
51
+ f"请将 is_secret 设为 true 或移除 source 字段"
52
+ )
53
+
54
+ # 校验:source 与 value 互斥
55
+ if source is not None and value is not None:
56
+ raise ValueError(
57
+ f"参数 '{name}' 不能同时配置 source 和 value,"
58
+ f"推荐使用 source: env,移除 value 字段"
59
+ )
60
+
61
+ # 校验:v1 仅支持 source: env
62
+ if source is not None and source != "env":
63
+ raise ValueError(
64
+ f"参数 '{name}' 的 source 值 '{source}' 不受支持,目前只支持 source: env"
65
+ )
66
+
67
+ if source == "env":
68
+ # 推荐写法:从环境变量读取真实值
69
+ if not env_var_name:
70
+ raise ValueError(
71
+ f"参数 '{name}' 配置了 source: env 但缺少 env 字段,"
72
+ f"请补充 env: <环境变量名>"
73
+ )
74
+
75
+ actual_value = os.environ.get(env_var_name)
76
+ # 环境变量不存在或为空字符串,均视为失败
77
+ if not actual_value:
78
+ raise ValueError(
79
+ f"参数 '{name}' 需要环境变量 '{env_var_name}',但该变量不存在或为空,"
80
+ f"请先执行 export {env_var_name}=<your_value>"
81
+ )
82
+
83
+ resolved[name] = {
84
+ "value": actual_value,
85
+ "is_secret": True,
86
+ }
87
+
88
+ elif is_secret and value is not None:
89
+ # 兼容旧写法:明文 secret,打印迁移提示,放行
90
+ sep = typer.style("─" * 52, fg=typer.colors.YELLOW)
91
+ title = typer.style(
92
+ f"⚠ 警告:参数 '{name}' 的 secret 值以明文写入了 openbayes.yaml",
93
+ fg=typer.colors.YELLOW,
94
+ bold=True,
95
+ )
96
+ typer.echo(f"\n{sep}", err=True)
97
+ typer.echo(title, err=True)
98
+ typer.echo(" 该文件可能随源码一起上传至服务端,建议改用环境变量方式:\n", err=True)
99
+ typer.echo(f" {name}:", err=True)
100
+ typer.echo(f" is_secret: true", err=True)
101
+ typer.echo(f" source: env", err=True)
102
+ typer.echo(f" env: {name}", err=True)
103
+ typer.echo(f"{sep}\n", err=True)
104
+ resolved[name] = raw_value
105
+
106
+ else:
107
+ # 其他 dict 形式参数(如只有 is_secret: true 但没有 value/source),直接透传
108
+ resolved[name] = raw_value
109
+
110
+ return resolved
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "openbayes-cli"
3
- version = "0.28.2"
3
+ version = "0.28.3"
4
4
  description = "A Python CLI for OpenBayes"
5
5
  authors = ["OpenBayes <dev@openbayes.com>"]
6
6
  readme = "README.md"
File without changes