Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/8513.enhance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
migrate unnecessary service related function from api to service
44 changes: 0 additions & 44 deletions src/ai/backend/manager/api/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@
from .utils import (
LegacyBaseRequestModel,
LegacyBaseResponseModel,
Undefined,
catch_unexpected,
check_api_params,
deprecated_stub,
Expand Down Expand Up @@ -313,49 +312,6 @@ def check_and_return(self, value: Any) -> object:
tx.AliasedKey(["attach_network", "attachNetwork"], default=None): t.Null | tx.UUID,
})

overwritten_param_check = t.Dict({
t.Key("template_id"): tx.UUID,
t.Key("session_name"): tx.SessionName,
t.Key("image", default=None): t.Null | t.String,
tx.AliasedKey(["session_type", "sess_type"]): tx.Enum(SessionTypes),
t.Key("group", default=None): t.Null | t.String,
t.Key("domain", default=None): t.Null | t.String,
t.Key("config", default=None): t.Null | t.Mapping(t.String, t.Any),
t.Key("tag", default=None): t.Null | t.String,
t.Key("enqueue_only", default=False): t.ToBool,
t.Key("max_wait_seconds", default=0): t.Int[0:],
t.Key("reuse", default=True): t.ToBool,
t.Key("startup_command", default=None): t.Null | t.String,
t.Key("bootstrap_script", default=None): t.Null | t.String,
t.Key("owner_access_key", default=None): t.Null | t.String,
tx.AliasedKey(["scaling_group", "scalingGroup"], default=None): t.Null | t.String,
tx.AliasedKey(["cluster_size", "clusterSize"], default=None): t.Null | t.Int[1:],
tx.AliasedKey(["cluster_mode", "clusterMode"], default="SINGLE_NODE"): tx.Enum(ClusterMode),
tx.AliasedKey(["starts_at", "startsAt"], default=None): t.Null | t.String,
tx.AliasedKey(["batch_timeout", "batchTimeout"], default=None): t.Null | tx.TimeDuration,
}).allow_extra("*")


def sub(d: dict[Any, Any], old: Any, new: Any) -> dict[Any, Any]:
for k, v in d.items():
if isinstance(v, (Mapping, dict)):
d[k] = sub(dict(v), old, new)
elif d[k] == old:
d[k] = new
return d


def drop_undefined(d: dict[Any, Any]) -> dict[Any, Any]:
newd: dict[Any, Any] = {}
for k, v in d.items():
if isinstance(v, (Mapping, dict)):
newval = drop_undefined(dict(v))
if len(newval.keys()) > 0: # exclude empty dict always
newd[k] = newval
elif not isinstance(v, Undefined):
newd[k] = v
return newd


async def query_userinfo(
request: web.Request,
Expand Down
11 changes: 6 additions & 5 deletions src/ai/backend/manager/services/session/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,6 @@
)
from ai.backend.logging.utils import BraceStyleAdapter
from ai.backend.manager.api.scaling_group import query_wsproxy_status
from ai.backend.manager.api.session import (
drop_undefined,
overwritten_param_check,
)
from ai.backend.manager.api.utils import undefined
from ai.backend.manager.bgtask.tasks.commit_session import CommitSessionManifest
from ai.backend.manager.bgtask.types import ManagerBgtaskName
Expand Down Expand Up @@ -193,7 +189,12 @@
UploadFilesAction,
UploadFilesActionResult,
)
from ai.backend.manager.services.session.types import CommitStatusInfo, LegacySessionInfo
from ai.backend.manager.services.session.types import (
CommitStatusInfo,
LegacySessionInfo,
overwritten_param_check,
)
from ai.backend.manager.services.session.utils import drop_undefined
from ai.backend.manager.sokovan.scheduling_controller import SchedulingController
from ai.backend.manager.types import UserScope

Expand Down
28 changes: 28 additions & 0 deletions src/ai/backend/manager/services/session/types.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,39 @@
from __future__ import annotations

import dataclasses
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, Optional
from uuid import UUID

import trafaret as t

from ai.backend.common import validators as tx
from ai.backend.common.types import ClusterMode, SessionTypes
from ai.backend.manager.data.session.types import SessionStatus

overwritten_param_check = t.Dict({
Comment on lines +11 to +15
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

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

services/session/types.py now pulls in trafaret/validators just to host overwritten_param_check. In this codebase, service types.py files appear to be kept as lightweight dataclass/type holders (e.g. services/vfolder/types.py, services/image/types.py), and services/session/types.py is imported by action modules that only need the dataclasses. Consider moving overwritten_param_check into a dedicated module (e.g. services/session/validators.py or schemas.py) so non-validation callers don’t pay the extra import-time dependency/overhead.

Copilot uses AI. Check for mistakes.
t.Key("template_id"): tx.UUID,
t.Key("session_name"): tx.SessionName,
t.Key("image", default=None): t.Null | t.String,
tx.AliasedKey(["session_type", "sess_type"]): tx.Enum(SessionTypes),
t.Key("group", default=None): t.Null | t.String,
t.Key("domain", default=None): t.Null | t.String,
t.Key("config", default=None): t.Null | t.Mapping(t.String, t.Any),
t.Key("tag", default=None): t.Null | t.String,
t.Key("enqueue_only", default=False): t.ToBool,
t.Key("max_wait_seconds", default=0): t.Int[0:],
t.Key("reuse", default=True): t.ToBool,
t.Key("startup_command", default=None): t.Null | t.String,
t.Key("bootstrap_script", default=None): t.Null | t.String,
t.Key("owner_access_key", default=None): t.Null | t.String,
tx.AliasedKey(["scaling_group", "scalingGroup"], default=None): t.Null | t.String,
tx.AliasedKey(["cluster_size", "clusterSize"], default=None): t.Null | t.Int[1:],
tx.AliasedKey(["cluster_mode", "clusterMode"], default="SINGLE_NODE"): tx.Enum(ClusterMode),
tx.AliasedKey(["starts_at", "startsAt"], default=None): t.Null | t.String,
tx.AliasedKey(["batch_timeout", "batchTimeout"], default=None): t.Null | tx.TimeDuration,
}).allow_extra("*")


@dataclass
class LegacySessionInfo:
Expand Down
18 changes: 18 additions & 0 deletions src/ai/backend/manager/services/session/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from __future__ import annotations

from collections.abc import Mapping
from typing import Any

from ai.backend.manager.api.utils import Undefined


def drop_undefined(d: dict[Any, Any]) -> dict[Any, Any]:
newd: dict[Any, Any] = {}
for k, v in d.items():
if isinstance(v, (Mapping, dict)):
newval = drop_undefined(dict(v))
if len(newval.keys()) > 0: # exclude empty dict always
Comment on lines +12 to +14
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

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

In drop_undefined(), isinstance(v, (Mapping, dict)) is redundant because dict is already a Mapping, and len(newval.keys()) > 0 does extra work. You can simplify to check only Mapping and use a truthiness check (if newval) to avoid allocating the keys() view and computing its length.

Suggested change
if isinstance(v, (Mapping, dict)):
newval = drop_undefined(dict(v))
if len(newval.keys()) > 0: # exclude empty dict always
if isinstance(v, Mapping):
newval = drop_undefined(dict(v))
if newval: # exclude empty dict always

Copilot uses AI. Check for mistakes.
newd[k] = newval
elif not isinstance(v, Undefined):
newd[k] = v
return newd
Loading