Skip to content
Closed
1 change: 1 addition & 0 deletions .idea/dictionaries/project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion swanlab/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
SwanLabEnv.check()

# 导出 OpenApi 接口,必须要等待上述的 import 语句执行完毕以后才能导出,否则会触发循环引用
from .api import OpenApi
from .api import OpenApi, Api

__version__ = get_package_version()

Expand All @@ -48,6 +48,7 @@
"get_config",
"config",
"OpenApi",
"Api",
"sync_wandb",
"sync_mlflow",
"sync_tensorboardX",
Expand Down
150 changes: 138 additions & 12 deletions swanlab/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,141 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
r"""
@DATE: 2025/4/29 9:40
@File: __init__.py
@IDE: pycharm
@Description:
SwanLab OpenAPI包
"""
@author: Zhou QiYang
@file: __init__.py
@time: 2026/1/5 17:58
@description: SwanLab OpenAPI包
"""

from typing import Optional, List, Dict

from swanlab.core_python import auth, Client
from swanlab.core_python.api.experiment import get_single_experiment, get_project_experiments
from swanlab.error import KeyFileError
from swanlab.log import swanlog
from swanlab.package import HostFormatter, get_key
from .deprecated import OpenApi
from .experiment import Experiment
from .experiments import Experiments
from .projects import Projects
from .user import User
from .workspace import Workspace
from .workspaces import Workspaces


class Api:
def __init__(self, api_key: Optional[str] = None, host: Optional[str] = None, web_host: Optional[str] = None):
"""
初始化 OpenApi 实例,用户需提前登录,或者提供API密钥
:param api_key: API 密钥,可选
:param host: API 主机地址,可选
:param web_host: Web 主机地址,可选
"""
if host or web_host:
HostFormatter(host, web_host)()
if api_key:
swanlog.debug("Using API key", api_key)
else:
swanlog.debug("Using existing key")
try:
api_key = get_key()
except KeyFileError as e:
swanlog.error("To use SwanLab OpenAPI, please login first.")
raise RuntimeError("Not logged in.") from e

self._login_info = auth.code_login(api_key, save_key=False)
# 一个OpenApi对应一个client,可创建多个api获取从不同的client获取不同账号下的实验信息
self._client: Client = Client(self._login_info)
self._web_host = self._login_info.web_host
self._login_user = self._login_info.username

def user(self, username: str = None) -> User:
"""
获取用户实例,用于操作用户相关信息
:param username: 指定用户名,如果为 None,则返回当前登录用户
:return: User 实例,可对当前/指定用户进行操作
"""
return User(client=self._client, login_user=self._login_user, username=username)

def projects(
self,
workspace: str,
sort: Optional[List[str]] = None,
search: Optional[str] = None,
detail: Optional[bool] = True,
) -> Projects:
"""
获取指定工作空间(组织)下的所有项目信息
:param workspace: 工作空间(组织)名称
:param sort: 排序方式,可选
:param search: 搜索关键词,可选
:param detail: 是否返回详细信息,可选
:return: Projects 实例,可遍历获取项目信息
"""
return Projects(
self._client,
web_host=self._web_host,
workspace=workspace,
sort=sort,
search=search,
detail=detail,
)

def runs(self, path: str, filters: Dict[str, object] = None) -> Experiments:
"""
获取指定项目下的所有实验信息
:param path: 项目路径,格式为 'username/project'
:return: Experiments 实例,可遍历获取实验信息
:param filters: 筛选实验的条件,可选
"""
return Experiments(self._client, path=path, login_info=self._login_info, filters=filters)

def run(
self,
path: str,
) -> Experiment:
"""
获取指定实验的信息
:param path: 实验路径,格式为 'username/project/run_id'
:return: Experiment 实例,包含实验信息
"""
# TODO: 待后端完善后替换成专用的接口
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The TODO comment indicates that the backend needs to be improved for the run method. This suggests a potential future refactoring or an incomplete implementation that might affect the reliability or functionality of this method. It's important to address such TODOs to ensure the API is fully robust.

if len(path.split('/')) != 3:
raise ValueError(f"User's {path} is invaded. Correct path should be like 'username/project/run_id'")
_data = get_single_experiment(self._client, path=path)
proj_path = path.rsplit('/', 1)[0]
data = get_project_experiments(
self._client, path=proj_path, filters={'name': _data['name'], 'created_at': _data['createdAt']}
)
return Experiment(
self._client,
data=data[0],
path=proj_path,
web_host=self._web_host,
login_user=self._login_user,
line_count=1,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The line_count parameter in the Experiment constructor is hardcoded to 1. This seems incorrect, as line_count is intended to represent the total number of historical experiments in the project. Hardcoding it to 1 will provide misleading information to the user when accessing experiment.history_line_count.

Suggested change
line_count=1,
line_count=len(data),

)

def workspaces(
self,
username: str = None,
):
"""
获取当前登录用户的工作空间迭代器
当username为其他用户时,可以作为visitor访问其工作空间
"""
if username is None:
username = self._login_user
return Workspaces(self._client, username=username)

def workspace(
self,
username: str = None,
):
"""
获取当前登录用户的工作空间
"""
if username is None:
username = self._login_user
return Workspace(client=self._client, workspace=username)

from swanlab.api.main import OpenApi

__all__ = [
"OpenApi"
]
__all__ = ["Api", "OpenApi"]
10 changes: 10 additions & 0 deletions swanlab/api/deprecated/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""
@author: Zhou QiYang
@file: __init__.py
@time: 2025/12/30 20:54
@description: 旧版OpenApi,即将遗弃
"""

from .main import OpenApi

__all__ = ["OpenApi"]
6 changes: 3 additions & 3 deletions swanlab/api/base.py → swanlab/api/deprecated/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@

import requests

from swanlab.api.types import ApiResponse
from swanlab.core_python import auth, create_session
from swanlab.log.log import SwanLog
from .types import ApiResponse

_logger: Optional[SwanLog] = None

Expand Down Expand Up @@ -64,7 +64,7 @@ def __init__(self, login_info: auth.LoginInfo):
self.__login_info: auth.LoginInfo = login_info
self.__session: requests.Session = self.__init_session()
self.service: OpenApiService = OpenApiService(self)

@property
def session(self) -> requests.Session:
"""
Expand Down Expand Up @@ -131,7 +131,7 @@ def get_project_info(self, username: str, projname: str) -> ApiResponse[dict]:
获取项目详情
"""
return self.http.get(f"/project/{username}/{projname}", params={})

@staticmethod
def fetch_paginated_api(
api_func: Callable[..., ApiResponse], # 分页 API 请求函数
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"""
from typing import List

from swanlab.api.base import ApiBase, ApiHTTP
from swanlab.api.types import ApiResponse, Experiment, Pagination
from .base import ApiBase, ApiHTTP
from .types import ApiResponse, Experiment, Pagination

try:
from pandas import DataFrame
Expand Down
4 changes: 2 additions & 2 deletions swanlab/api/group.py → swanlab/api/deprecated/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
组织相关的开放API
"""

from swanlab.api.base import ApiBase, ApiHTTP
from swanlab.api.types import ApiResponse
from .base import ApiBase, ApiHTTP
from .types import ApiResponse


class GroupAPI(ApiBase):
Expand Down
13 changes: 7 additions & 6 deletions swanlab/api/main.py → swanlab/api/deprecated/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
"""
from typing import Dict, List, Union

from swanlab.api.base import ApiHTTP, get_logger
from swanlab.api.experiment import ExperimentAPI
from swanlab.api.group import GroupAPI
from swanlab.api.project import ProjectAPI
from swanlab.api.types import ApiResponse, Experiment, Project
from swanlab.core_python import auth
from swanlab.error import KeyFileError
from swanlab.log.log import SwanLog
from swanlab.package import get_key
from .base import ApiHTTP, get_logger
from .experiment import ExperimentAPI
from .group import GroupAPI
from .project import ProjectAPI
from .types import ApiResponse, Experiment, Project

try:
from pandas import DataFrame
Expand All @@ -28,6 +28,7 @@
class OpenApi:
def __init__(self, api_key: str = "", log_level: str = "info"):
self.__logger: SwanLog = get_logger(log_level)
self.__logger.warning("OpenApi will be soon deprecated in swanlab 0.8.0. Please use swanlab.Api() instead.")

if api_key:
self.__logger.debug("Using API key", api_key)
Expand Down Expand Up @@ -210,7 +211,7 @@ def get_summary(
return self.experiment.get_summary(
exp_id=exp_id,
pro_id=project_cuid,
root_exp_id=exp.data.get("rootExpId", ""),
root_exp_id=exp.data.get("rootExpId", ""),
root_pro_id=exp.data.get("rootProId", "")
)

Expand Down
4 changes: 2 additions & 2 deletions swanlab/api/project.py → swanlab/api/deprecated/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
项目相关的开放API
"""

from swanlab.api.base import ApiBase, ApiHTTP
from swanlab.api.types import ApiResponse, Pagination, Project
from .base import ApiBase, ApiHTTP
from .types import ApiResponse, Pagination, Project


class ProjectAPI(ApiBase):
Expand Down
File renamed without changes.
Loading