Skip to content

Commit 20cf70b

Browse files
jopemachineclaude
andcommitted
feat(BA-5844): AppConfigPolicy REST v2 surface
Add the REST v2 endpoints for AppConfigPolicy on top of the GraphQL- only BA-5815. Mirrors the BA-5829 / BA-5830 split for the AppConfig- Fragment stack. - `api/rest/v2/app_config_policy/handler.py` — `V2AppConfigPolicyHandler` exposing `get`, `search`, and admin bulk-create / bulk-update / bulk-purge endpoints, all delegating to `AppConfigPolicyAdapter`. - `api/rest/v2/app_config_policy/registry.py` — route registrar for the `app-config-policies` sub-tree (reads via `auth_required`, bulk writes via `superadmin_required`). - `api/rest/v2/path_params.py` — `AppConfigPolicyConfigNamePathParam` for path-bound `config_name` lookups. - `api/rest/v2/tree.py` — wire up the new handler / registrar. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 83d557b commit 20cf70b

5 files changed

Lines changed: 124 additions & 0 deletions

File tree

src/ai/backend/manager/api/rest/v2/app_config_policy/__init__.py

Whitespace-only changes.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
"""REST v2 handler for the app-config policy domain.
2+
3+
Writes are **bulk-only** — the single-item create / update / purge
4+
endpoints were removed in favour of `/bulk-create`, `/bulk-update`,
5+
`/bulk-purge` (admin-only).
6+
"""
7+
8+
from __future__ import annotations
9+
10+
import logging
11+
from http import HTTPStatus
12+
from typing import TYPE_CHECKING, Final
13+
14+
from ai.backend.common.api_handlers import APIResponse, BodyParam, PathParam
15+
from ai.backend.common.dto.manager.v2.app_config_policy.request import (
16+
AdminBulkCreateAppConfigPoliciesInput,
17+
AdminBulkPurgeAppConfigPoliciesInput,
18+
AdminBulkUpdateAppConfigPoliciesInput,
19+
SearchAppConfigPoliciesInput,
20+
)
21+
from ai.backend.logging import BraceStyleAdapter
22+
from ai.backend.manager.api.rest.v2.path_params import AppConfigPolicyConfigNamePathParam
23+
24+
if TYPE_CHECKING:
25+
from ai.backend.manager.api.adapters.app_config_policy import AppConfigPolicyAdapter
26+
27+
log: Final = BraceStyleAdapter(logging.getLogger(__spec__.name))
28+
29+
30+
class V2AppConfigPolicyHandler:
31+
"""REST v2 handler for app-config policy operations."""
32+
33+
def __init__(self, *, adapter: AppConfigPolicyAdapter) -> None:
34+
self._adapter = adapter
35+
36+
# ── Reads ────────────────────────────────────────────────────
37+
38+
async def get(
39+
self,
40+
path: PathParam[AppConfigPolicyConfigNamePathParam],
41+
) -> APIResponse:
42+
"""Read a single policy by `config_name` (any authenticated user)."""
43+
result = await self._adapter.get(path.parsed.config_name)
44+
return APIResponse.build(status_code=HTTPStatus.OK, response_model=result)
45+
46+
async def search(
47+
self,
48+
body: BodyParam[SearchAppConfigPoliciesInput],
49+
) -> APIResponse:
50+
"""Paginated policy search (any authenticated user)."""
51+
result = await self._adapter.search(body.parsed)
52+
return APIResponse.build(status_code=HTTPStatus.OK, response_model=result)
53+
54+
# ── Admin bulk writes ────────────────────────────────────────
55+
56+
async def admin_bulk_create(
57+
self,
58+
body: BodyParam[AdminBulkCreateAppConfigPoliciesInput],
59+
) -> APIResponse:
60+
"""Strict insert; per-item transactions (admin only)."""
61+
result = await self._adapter.admin_bulk_create(body.parsed)
62+
return APIResponse.build(status_code=HTTPStatus.OK, response_model=result)
63+
64+
async def admin_bulk_update(
65+
self,
66+
body: BodyParam[AdminBulkUpdateAppConfigPoliciesInput],
67+
) -> APIResponse:
68+
"""Replace `scope_sources` (admin only). `config_name` is immutable."""
69+
result = await self._adapter.admin_bulk_update(body.parsed)
70+
return APIResponse.build(status_code=HTTPStatus.OK, response_model=result)
71+
72+
async def admin_bulk_purge(
73+
self,
74+
body: BodyParam[AdminBulkPurgeAppConfigPoliciesInput],
75+
) -> APIResponse:
76+
"""Hard-delete (admin only); referenced `config_name`s fail per-item."""
77+
result = await self._adapter.admin_bulk_purge(body.parsed)
78+
return APIResponse.build(status_code=HTTPStatus.OK, response_model=result)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""Route registration for v2 app-config policy endpoints."""
2+
3+
from __future__ import annotations
4+
5+
from typing import TYPE_CHECKING
6+
7+
from ai.backend.manager.api.rest.middleware.auth import auth_required, superadmin_required
8+
from ai.backend.manager.api.rest.routing import RouteRegistry
9+
10+
from .handler import V2AppConfigPolicyHandler
11+
12+
if TYPE_CHECKING:
13+
from ai.backend.manager.api.rest.types import RouteDeps
14+
15+
16+
def register_v2_app_config_policy_routes(
17+
handler: V2AppConfigPolicyHandler,
18+
route_deps: RouteDeps,
19+
) -> RouteRegistry:
20+
"""Register all v2 app-config policy routes.
21+
22+
Reads (`GET /{config_name}`, `POST /search`) are available to any
23+
authenticated user. Writes are bulk-only and admin-only —
24+
`/bulk-create`, `/bulk-update`, `/bulk-purge`.
25+
"""
26+
reg = RouteRegistry.create("app-config-policies", route_deps.cors_options)
27+
28+
# Reads
29+
reg.add("POST", "/search", handler.search, middlewares=[auth_required])
30+
reg.add("GET", "/{config_name}", handler.get, middlewares=[auth_required])
31+
# Admin bulk writes
32+
reg.add("POST", "/bulk-create", handler.admin_bulk_create, middlewares=[superadmin_required])
33+
reg.add("POST", "/bulk-update", handler.admin_bulk_update, middlewares=[superadmin_required])
34+
reg.add("POST", "/bulk-purge", handler.admin_bulk_purge, middlewares=[superadmin_required])
35+
36+
return reg

src/ai/backend/manager/api/rest/v2/path_params.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
from ai.backend.common.api_handlers import BaseRequestModel
1010

1111

12+
class AppConfigPolicyConfigNamePathParam(BaseRequestModel):
13+
config_name: str = Field(description="App-config policy `config_name`")
14+
15+
1216
class DomainNamePathParam(BaseRequestModel):
1317
domain_name: str = Field(description="Domain name")
1418

src/ai/backend/manager/api/rest/v2/tree.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ def build_v2_routes(
2828
# Lazy imports to avoid circular dependencies at module level
2929
from .agent.handler import V2AgentHandler
3030
from .agent.registry import register_v2_agent_routes
31+
from .app_config_policy.handler import V2AppConfigPolicyHandler
32+
from .app_config_policy.registry import register_v2_app_config_policy_routes
3133
from .artifact.handler import V2ArtifactHandler
3234
from .artifact.registry import register_v2_artifact_routes
3335
from .artifact_registry.handler import V2ArtifactRegistryHandler
@@ -115,6 +117,7 @@ def build_v2_routes(
115117

116118
# Build all handlers (each takes its individual adapter)
117119
agent_handler = V2AgentHandler(adapter=adapters.agent)
120+
app_config_policy_handler = V2AppConfigPolicyHandler(adapter=adapters.app_config_policy)
118121
artifact_handler = V2ArtifactHandler(adapter=adapters.artifact)
119122
artifact_registry_handler = V2ArtifactRegistryHandler(adapter=adapters.artifact_registry)
120123
audit_log_handler = V2AuditLogHandler(adapter=adapters.audit_log)
@@ -171,6 +174,9 @@ def build_v2_routes(
171174

172175
# Add all domain sub-registries
173176
v2_reg.add_subregistry(register_v2_agent_routes(agent_handler, route_deps))
177+
v2_reg.add_subregistry(
178+
register_v2_app_config_policy_routes(app_config_policy_handler, route_deps)
179+
)
174180
v2_reg.add_subregistry(register_v2_artifact_routes(artifact_handler, route_deps))
175181
v2_reg.add_subregistry(
176182
register_v2_artifact_registry_routes(artifact_registry_handler, route_deps)

0 commit comments

Comments
 (0)