Skip to content

Commit 653f24d

Browse files
HyeockJinKimclaude
andauthored
feat(BA-4538): Add System version info to Client SDK v2 (#9060)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 770aab4 commit 653f24d

File tree

6 files changed

+125
-0
lines changed

6 files changed

+125
-0
lines changed

changes/9060.feature.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add System version info client (`SystemClient.get_versions()`) to Client SDK v2
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from __future__ import annotations
2+
3+
from ai.backend.client.v2.base_domain import BaseDomainClient
4+
from ai.backend.common.dto.manager.system import SystemVersionResponse
5+
6+
7+
class SystemClient(BaseDomainClient):
8+
"""Client for system version info endpoint."""
9+
10+
async def get_versions(self) -> SystemVersionResponse:
11+
return await self._client.typed_request(
12+
"GET",
13+
"/",
14+
response_model=SystemVersionResponse,
15+
)

src/ai/backend/client/v2/registry.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from .domains.session import SessionClient
3636
from .domains.storage import StorageClient
3737
from .domains.streaming import StreamingClient
38+
from .domains.system import SystemClient
3839
from .domains.template import TemplateClient
3940
from .domains.vfolder import VFolderClient
4041

@@ -230,3 +231,9 @@ def compute_session(self) -> ComputeSessionClient:
230231
from .domains.compute_session import ComputeSessionClient
231232

232233
return ComputeSessionClient(self._client)
234+
235+
@cached_property
236+
def system(self) -> SystemClient:
237+
from .domains.system import SystemClient
238+
239+
return SystemClient(self._client)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from __future__ import annotations
2+
3+
from .response import (
4+
SystemVersionResponse,
5+
)
6+
7+
__all__ = ("SystemVersionResponse",)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from __future__ import annotations
2+
3+
from pydantic import Field
4+
5+
from ai.backend.common.api_handlers import BaseResponseModel
6+
7+
__all__ = ("SystemVersionResponse",)
8+
9+
10+
class SystemVersionResponse(BaseResponseModel):
11+
"""Response for system version info from GET /."""
12+
13+
version: str = Field(description="API version string")
14+
manager: str = Field(description="Manager version string")
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from typing import Any
2+
from unittest.mock import AsyncMock, MagicMock
3+
4+
import pytest
5+
from yarl import URL
6+
7+
from ai.backend.client.v2.base_client import BackendAIClient
8+
from ai.backend.client.v2.config import ClientConfig
9+
from ai.backend.client.v2.domains.system import SystemClient
10+
from ai.backend.common.dto.manager.system import SystemVersionResponse
11+
12+
from .conftest import MockAuth
13+
14+
_DEFAULT_CONFIG = ClientConfig(endpoint=URL("https://api.example.com"))
15+
16+
17+
def _make_client(
18+
mock_session: MagicMock | None = None,
19+
config: ClientConfig | None = None,
20+
) -> BackendAIClient:
21+
return BackendAIClient(
22+
config or _DEFAULT_CONFIG,
23+
MockAuth(),
24+
mock_session or MagicMock(),
25+
)
26+
27+
28+
def _make_request_session(resp: AsyncMock) -> MagicMock:
29+
"""Build a mock session whose ``request()`` returns *resp* as a context manager."""
30+
mock_ctx = AsyncMock()
31+
mock_ctx.__aenter__ = AsyncMock(return_value=resp)
32+
mock_ctx.__aexit__ = AsyncMock(return_value=False)
33+
34+
mock_session = MagicMock()
35+
mock_session.request = MagicMock(return_value=mock_ctx)
36+
return mock_session
37+
38+
39+
def _ok_response(data: dict[str, Any]) -> AsyncMock:
40+
mock_resp = AsyncMock()
41+
mock_resp.status = 200
42+
mock_resp.json = AsyncMock(return_value=data)
43+
return mock_resp
44+
45+
46+
class TestSystemClient:
47+
@pytest.mark.asyncio
48+
async def test_get_versions(self) -> None:
49+
mock_resp = _ok_response({
50+
"version": "v9.20250722",
51+
"manager": "25.3.0",
52+
})
53+
mock_session = _make_request_session(mock_resp)
54+
client = _make_client(mock_session)
55+
system = SystemClient(client)
56+
57+
result = await system.get_versions()
58+
59+
assert isinstance(result, SystemVersionResponse)
60+
assert result.version == "v9.20250722"
61+
assert result.manager == "25.3.0"
62+
63+
call_args = mock_session.request.call_args
64+
assert call_args.args[0] == "GET"
65+
assert str(call_args.args[1]).endswith("/")
66+
67+
@pytest.mark.asyncio
68+
async def test_get_versions_different_values(self) -> None:
69+
mock_resp = _ok_response({
70+
"version": "v8.20240315",
71+
"manager": "24.1.0",
72+
})
73+
mock_session = _make_request_session(mock_resp)
74+
client = _make_client(mock_session)
75+
system = SystemClient(client)
76+
77+
result = await system.get_versions()
78+
79+
assert isinstance(result, SystemVersionResponse)
80+
assert result.version == "v8.20240315"
81+
assert result.manager == "24.1.0"

0 commit comments

Comments
 (0)