Skip to content

Commit aeb4461

Browse files
committed
feat: add api placeholder
1 parent a281007 commit aeb4461

12 files changed

Lines changed: 474 additions & 9 deletions

File tree

swanlab/sdk/internal/core_python/api/__init__.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,54 @@
33
@file: __init__.py
44
@time: 2026/3/7 18:19
55
@description: SwanLab 运行时 API 封装
6+
67
绝大多数API使用 Client 对象,少部分API使用requests库直接调用
78
我们以rpc风格封装API,方便调用
89
"""
10+
11+
from .experiment import (
12+
create_or_resume_experiment,
13+
delete_experiment,
14+
get_experiment_metrics,
15+
get_project_experiments,
16+
get_single_experiment,
17+
send_experiment_heartbeat,
18+
update_experiment_state,
19+
)
20+
from .project import delete_project, get_or_create_project, get_project, get_workspace_projects
21+
from .self_hosted import create_user, get_self_hosted_init, get_users
22+
from .user import (
23+
create_api_key,
24+
delete_api_key,
25+
get_api_keys,
26+
get_latest_api_key,
27+
get_user_groups,
28+
get_workspace_info,
29+
)
30+
31+
__all__ = [
32+
# experiment
33+
"create_or_resume_experiment",
34+
"send_experiment_heartbeat",
35+
"update_experiment_state",
36+
"get_project_experiments",
37+
"get_single_experiment",
38+
"get_experiment_metrics",
39+
"delete_experiment",
40+
# project
41+
"get_project",
42+
"get_or_create_project",
43+
"get_workspace_projects",
44+
"delete_project",
45+
# user
46+
"create_api_key",
47+
"delete_api_key",
48+
"get_user_groups",
49+
"get_workspace_info",
50+
"get_api_keys",
51+
"get_latest_api_key",
52+
# self_hosted
53+
"get_self_hosted_init",
54+
"create_user",
55+
"get_users",
56+
]

swanlab/sdk/internal/core_python/api/experiment.py

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
@description: SwanLab 运行时实验API
66
"""
77

8-
from typing import List, Literal, Optional
8+
from typing import Dict, List, Literal, Optional, Union
99

1010
from google.protobuf.timestamp_pb2 import Timestamp
1111

@@ -14,7 +14,10 @@
1414
from swanlab.sdk.internal.core_python import client
1515
from swanlab.sdk.internal.pkg import helper
1616
from swanlab.sdk.typings.core_python.api.experiment import InitExperimentType
17-
from swanlab.sdk.typings.run import ResumeType
17+
from swanlab.sdk.typings.core_python.api.experiment import RunType
18+
from swanlab.sdk.typings.run import ResumeType, RunStateType
19+
20+
from .utils import parse_column_type, to_camel_case
1821

1922

2023
def create_or_resume_experiment(
@@ -91,3 +94,93 @@ def stop_experiment(username: str, project: str, cuid: str, *, state: RunState,
9194
"from": "sdk",
9295
},
9396
)
97+
return resp.raw.status_code == 201
98+
99+
100+
def send_experiment_heartbeat(*, cuid: str, flag_id: str) -> None:
101+
"""
102+
发送实验心跳,保持实验处于活跃状态
103+
:param cuid: 实验唯一标识符
104+
:param flag_id: 实验标记ID
105+
"""
106+
client.post(f"/house/experiments/{cuid}/heartbeat", {"flagId": flag_id})
107+
108+
109+
def update_experiment_state(
110+
*,
111+
username: str,
112+
projname: str,
113+
cuid: str,
114+
state: RunStateType,
115+
finished_at: Optional[str] = None,
116+
) -> None:
117+
"""
118+
更新实验状态
119+
:param username: 实验所属用户名
120+
:param projname: 实验所属项目名称
121+
:param cuid: 实验唯一标识符
122+
:param state: 实验状态
123+
:param finished_at: 实验结束时间,格式为 ISO 8601,如果不提供则使用当前时间
124+
"""
125+
put_data = {
126+
"state": state,
127+
"finishedAt": finished_at,
128+
"from": "sdk",
129+
}
130+
put_data = {k: v for k, v in put_data.items() if v is not None}
131+
client.put(f"/project/{username}/{projname}/runs/{cuid}/state", put_data)
132+
133+
134+
def get_project_experiments(
135+
*,
136+
path: str,
137+
filters: Optional[Dict[str, object]] = None,
138+
) -> Union[List[RunType], Dict[str, List[RunType]]]:
139+
"""
140+
获取指定项目下的所有实验信息
141+
若有实验分组,则返回一个字典,使用时需递归展平实验数据
142+
:param path: 项目路径 username/project
143+
:param filters: 筛选实验的条件,可选
144+
"""
145+
parsed_filters = (
146+
[
147+
{
148+
"key": to_camel_case(key) if parse_column_type(key) == "STABLE" else key.split(".", 1)[-1],
149+
"active": True,
150+
"value": [value],
151+
"op": "EQ",
152+
"type": parse_column_type(key),
153+
}
154+
for key, value in filters.items()
155+
]
156+
if filters
157+
else []
158+
)
159+
return client.post(f"/project/{path}/runs/shows", data={"filters": parsed_filters}).data
160+
161+
162+
def get_single_experiment(*, path: str) -> RunType:
163+
"""
164+
获取指定实验信息
165+
:param path: 实验路径 username/project/expid
166+
"""
167+
proj_path, expid = path.rsplit("/", 1)
168+
return client.get(f"/project/{proj_path}/runs/{expid}").data
169+
170+
171+
def get_experiment_metrics(*, expid: str, key: str) -> Dict[str, str]:
172+
"""
173+
获取指定字段的指标数据,返回csv网址
174+
:param expid: 实验cuid
175+
:param key: 指定字段列表
176+
"""
177+
return client.get(f"/experiment/{expid}/column/csv", params={"key": key}).data
178+
179+
180+
def delete_experiment(*, path: str) -> None:
181+
"""
182+
删除指定实验
183+
:param path: 实验路径 'username/project/expid'
184+
"""
185+
proj_path, expid = path.rsplit("/", 1)
186+
client.delete(f"/project/{proj_path}/runs/{expid}")

swanlab/sdk/internal/core_python/api/project.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@
1111
from swanlab.sdk.internal.core_python import client
1212
from swanlab.sdk.internal.pkg import helper
1313
from swanlab.sdk.internal.pkg.client.utils import decode_response
14-
from swanlab.sdk.typings.core_python.api.project import InitProjectType, ProjectType
14+
from swanlab.sdk.typings.core_python.api.project import InitProjectType, ProjectType, ProjResponseType
1515

1616

1717
def get_project(*, username: str, name: str) -> ProjectType:
1818
"""
19-
获取项目信息
19+
获取项目详情信息
2020
:param username: 项目所属的用户名
2121
:param name: 项目名称
2222
:return: 项目信息
@@ -42,3 +42,33 @@ def get_or_create_project(*, username: Optional[str], name: str, public: bool) -
4242
else:
4343
# 此接口为后端处理,sdk 在理论上不会出现其他错误,因此不需要处理其他错误
4444
raise e
45+
46+
47+
def get_workspace_projects(
48+
*,
49+
path: str,
50+
page: int = 1,
51+
size: int = 20,
52+
sort: Optional[str] = None,
53+
search: Optional[str] = None,
54+
detail: Optional[bool] = True,
55+
) -> ProjResponseType:
56+
"""
57+
获取指定页数和条件下的项目信息
58+
:param path: 工作空间名称
59+
:param page: 页码
60+
:param size: 每页项目数量
61+
:param sort: 排序规则, 可选
62+
:param search: 搜索的项目名称关键字, 可选
63+
:param detail: 是否包含项目下实验的相关信息, 可选, 默认为true
64+
"""
65+
params = {"page": page, "size": size, "sort": sort, "search": search, "detail": detail}
66+
return client.get(f"/project/{path}", params=helper.strip_none(params, strip_empty_str=True)).data
67+
68+
69+
def delete_project(*, path: str) -> None:
70+
"""
71+
删除指定项目
72+
:param path: 项目路径 'username/project'
73+
"""
74+
client.delete(f"/project/{path}")
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""
2+
@author: cunyue
3+
@file: self_hosted.py
4+
@time: 2026/4/14 19:00
5+
@description: SwanLab 私有化部署API
6+
"""
7+
8+
from swanlab.sdk.internal.core_python import client
9+
from swanlab.sdk.typings.core_python.api.user import SelfHostedInfoType
10+
11+
12+
def get_self_hosted_init() -> SelfHostedInfoType:
13+
"""
14+
获取私有化部署信息
15+
"""
16+
return client.get("/self_hosted/info").data
17+
18+
19+
def create_user(*, username: str, password: str) -> None:
20+
"""
21+
添加用户(私有化管理员限定)
22+
:param username: 用户名
23+
:param password: 用户密码
24+
"""
25+
data = {"users": [{"username": username, "password": password}]}
26+
client.post("/self_hosted/users", data=data)
27+
28+
29+
def get_users(*, page: int = 1, size: int = 20):
30+
"""
31+
分页获取用户(管理员限定)
32+
:param page: 页码
33+
:param size: 每页大小
34+
"""
35+
params = {"page": page, "size": size}
36+
return client.get("/self_hosted/users", params=params).data
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""
2+
@author: cunyue
3+
@file: user.py
4+
@time: 2026/4/14 19:00
5+
@description: SwanLab 运行时用户API
6+
"""
7+
8+
from typing import List, Optional
9+
10+
from swanlab.sdk.internal.core_python import client
11+
from swanlab.sdk.typings.core_python.api.user import ApiKeyType, GroupType
12+
from swanlab.sdk.typings.core_python.api.workspace import WorkspaceInfoType
13+
14+
15+
def create_api_key(*, name: Optional[str] = None) -> None:
16+
"""
17+
创建一个api_key
18+
:param name: api_key 的名称
19+
"""
20+
client.post("/user/key", data={"name": name} if name else None)
21+
22+
23+
def delete_api_key(*, key_id: int) -> None:
24+
"""
25+
删除指定id的api_key
26+
:param key_id: api_key的id
27+
"""
28+
client.delete(f"/user/key/{key_id}")
29+
30+
31+
def get_user_groups(*, username: str) -> List[GroupType]:
32+
"""
33+
获取用户加入的组织
34+
:param username: 用户名称
35+
"""
36+
return client.get(f"/user/{username}/groups").data
37+
38+
39+
def get_workspace_info(*, path: str) -> WorkspaceInfoType:
40+
"""
41+
获取指定工作空间的信息
42+
:param path: 工作空间名称
43+
"""
44+
return client.get(f"/group/{path}").data
45+
46+
47+
def get_api_keys() -> List[ApiKeyType]:
48+
"""
49+
获取当前全部的api_key
50+
"""
51+
return client.get("/user/key").data
52+
53+
54+
def get_latest_api_key() -> ApiKeyType:
55+
"""
56+
获取最新的api_key
57+
"""
58+
return client.get("/user/key/latest").data
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""
2+
@author: cunyue
3+
@file: utils.py
4+
@time: 2026/4/14 19:00
5+
@description: API工具函数
6+
"""
7+
8+
from swanlab.sdk.typings.run import SidebarItemType
9+
10+
11+
def parse_column_type(column: str) -> SidebarItemType:
12+
"""从前缀中获取指标类型"""
13+
column_type = column.split(".", 1)[0]
14+
if column_type == "summary":
15+
return "SCALAR"
16+
elif column_type == "config":
17+
return "CONFIG"
18+
else:
19+
return "STABLE"
20+
21+
22+
def to_camel_case(name: str) -> str:
23+
"""将下划线命名转化为驼峰命名"""
24+
return "".join([w.capitalize() if i > 0 else w for i, w in enumerate(name.split("_"))])

swanlab/sdk/typings/core_python/api/__init__.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,23 @@
33
@file: __init__.py
44
@time: 2026/3/7 18:40
55
@description: SwanLab API类型提示
6-
所有后端响应类型命名以 Response 结尾
6+
7+
所有后端响应类型命名以 Type 结尾
78
"""
9+
10+
from .experiment import RunType
11+
from .project import InitProjectType, ProjectLabelType, ProjectType, ProjResponseType
12+
from .user import ApiKeyType, GroupType, SelfHostedInfoType
13+
from .workspace import WorkspaceInfoType
14+
15+
__all__ = [
16+
"RunType",
17+
"ProjectType",
18+
"InitProjectType",
19+
"ProjResponseType",
20+
"ProjectLabelType",
21+
"GroupType",
22+
"ApiKeyType",
23+
"SelfHostedInfoType",
24+
"WorkspaceInfoType",
25+
]

0 commit comments

Comments
 (0)