Skip to content
Draft
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/11312.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add `AppConfigPolicy` REST v2 surface (`POST /v2/app-config-policies/search`, `GET /v2/app-config-policies/{policy_id}`, admin `bulk-create` / `bulk-update` / `bulk-purge`) β€” pairs with the GraphQL surface from BA-5815.
Empty file.
78 changes: 78 additions & 0 deletions src/ai/backend/manager/api/rest/v2/app_config_policy/handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""REST v2 handler for the app-config policy domain.

Writes are **bulk-only** β€” the single-item create / update / purge
endpoints were removed in favour of `/bulk-create`, `/bulk-update`,
`/bulk-purge` (admin-only).
"""

from __future__ import annotations

import logging
from http import HTTPStatus
from typing import TYPE_CHECKING, Final

from ai.backend.common.api_handlers import APIResponse, BodyParam, PathParam
from ai.backend.common.dto.manager.v2.app_config_policy.request import (
AdminBulkCreateAppConfigPoliciesInput,
AdminBulkPurgeAppConfigPoliciesInput,
AdminBulkUpdateAppConfigPoliciesInput,
SearchAppConfigPoliciesInput,
)
from ai.backend.logging import BraceStyleAdapter
from ai.backend.manager.api.rest.v2.path_params import AppConfigPolicyIdPathParam

if TYPE_CHECKING:
from ai.backend.manager.api.adapters.app_config_policy import AppConfigPolicyAdapter

log: Final = BraceStyleAdapter(logging.getLogger(__spec__.name))


class V2AppConfigPolicyHandler:
"""REST v2 handler for app-config policy operations."""

def __init__(self, *, adapter: AppConfigPolicyAdapter) -> None:
self._adapter = adapter

# ── Reads ────────────────────────────────────────────────────

async def get(
self,
path: PathParam[AppConfigPolicyIdPathParam],
) -> APIResponse:
"""Read a single policy by row id (any authenticated user)."""
result = await self._adapter.get(path.parsed.policy_id)
return APIResponse.build(status_code=HTTPStatus.OK, response_model=result)

async def search(
self,
body: BodyParam[SearchAppConfigPoliciesInput],
) -> APIResponse:
"""Paginated policy search (any authenticated user)."""
result = await self._adapter.search(body.parsed)
return APIResponse.build(status_code=HTTPStatus.OK, response_model=result)

# ── Admin bulk writes ────────────────────────────────────────

async def admin_bulk_create(
self,
body: BodyParam[AdminBulkCreateAppConfigPoliciesInput],
) -> APIResponse:
"""Strict insert; per-item transactions (admin only)."""
result = await self._adapter.admin_bulk_create(body.parsed)
return APIResponse.build(status_code=HTTPStatus.OK, response_model=result)

async def admin_bulk_update(
self,
body: BodyParam[AdminBulkUpdateAppConfigPoliciesInput],
) -> APIResponse:
"""Replace `scope_sources` (admin only). `config_name` is immutable."""
result = await self._adapter.admin_bulk_update(body.parsed)
return APIResponse.build(status_code=HTTPStatus.OK, response_model=result)

async def admin_bulk_purge(
self,
body: BodyParam[AdminBulkPurgeAppConfigPoliciesInput],
) -> APIResponse:
"""Hard-delete by row id (admin only); rows still referenced by fragments fail per-item."""
result = await self._adapter.admin_bulk_purge(body.parsed)
return APIResponse.build(status_code=HTTPStatus.OK, response_model=result)
36 changes: 36 additions & 0 deletions src/ai/backend/manager/api/rest/v2/app_config_policy/registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Route registration for v2 app-config policy endpoints."""

from __future__ import annotations

from typing import TYPE_CHECKING

from ai.backend.manager.api.rest.middleware.auth import auth_required, superadmin_required
from ai.backend.manager.api.rest.routing import RouteRegistry

from .handler import V2AppConfigPolicyHandler

if TYPE_CHECKING:
from ai.backend.manager.api.rest.types import RouteDeps


def register_v2_app_config_policy_routes(
handler: V2AppConfigPolicyHandler,
route_deps: RouteDeps,
) -> RouteRegistry:
"""Register all v2 app-config policy routes.

Reads (`GET /{policy_id}`, `POST /search`) are available to any
authenticated user. Writes are bulk-only and admin-only β€”
`/bulk-create`, `/bulk-update`, `/bulk-purge`.
"""
reg = RouteRegistry.create("app-config-policies", route_deps.cors_options)

# Reads
reg.add("POST", "/search", handler.search, middlewares=[auth_required])
reg.add("GET", "/{policy_id}", handler.get, middlewares=[auth_required])
# Admin bulk writes
reg.add("POST", "/bulk-create", handler.admin_bulk_create, middlewares=[superadmin_required])
reg.add("POST", "/bulk-update", handler.admin_bulk_update, middlewares=[superadmin_required])
reg.add("POST", "/bulk-purge", handler.admin_bulk_purge, middlewares=[superadmin_required])

return reg
4 changes: 4 additions & 0 deletions src/ai/backend/manager/api/rest/v2/path_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
from ai.backend.common.api_handlers import BaseRequestModel


class AppConfigPolicyIdPathParam(BaseRequestModel):
policy_id: UUID = Field(description="App-config policy row UUID")


class DomainNamePathParam(BaseRequestModel):
domain_name: str = Field(description="Domain name")

Expand Down
6 changes: 6 additions & 0 deletions src/ai/backend/manager/api/rest/v2/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ def build_v2_routes(
# Lazy imports to avoid circular dependencies at module level
from .agent.handler import V2AgentHandler
from .agent.registry import register_v2_agent_routes
from .app_config_policy.handler import V2AppConfigPolicyHandler
from .app_config_policy.registry import register_v2_app_config_policy_routes
from .artifact.handler import V2ArtifactHandler
from .artifact.registry import register_v2_artifact_routes
from .artifact_registry.handler import V2ArtifactRegistryHandler
Expand Down Expand Up @@ -115,6 +117,7 @@ def build_v2_routes(

# Build all handlers (each takes its individual adapter)
agent_handler = V2AgentHandler(adapter=adapters.agent)
app_config_policy_handler = V2AppConfigPolicyHandler(adapter=adapters.app_config_policy)
artifact_handler = V2ArtifactHandler(adapter=adapters.artifact)
artifact_registry_handler = V2ArtifactRegistryHandler(adapter=adapters.artifact_registry)
audit_log_handler = V2AuditLogHandler(adapter=adapters.audit_log)
Expand Down Expand Up @@ -171,6 +174,9 @@ def build_v2_routes(

# Add all domain sub-registries
v2_reg.add_subregistry(register_v2_agent_routes(agent_handler, route_deps))
v2_reg.add_subregistry(
register_v2_app_config_policy_routes(app_config_policy_handler, route_deps)
)
v2_reg.add_subregistry(register_v2_artifact_routes(artifact_handler, route_deps))
v2_reg.add_subregistry(
register_v2_artifact_registry_routes(artifact_registry_handler, route_deps)
Expand Down
Loading