Skip to content

Commit d3191d2

Browse files
jopemachineclaude
andcommitted
fix(BA-5832): align gql/adapters with BA-5829 split-adapter + lazy-load layout
The `-X theirs` rebase onto BA-5829 lost the merged-view adapter split (`AppConfigFragmentAdapter` -> `AppConfigAdapter` for merged views) and the import-cycle fix (lightened `app_config_fragment/__init__` + `strawberry.lazy()` on `AppConfigGQL.fragments` + JSON scalar on fragment payload fields). Re-apply BA-5829's versions of: - `api/adapters/app_config.py` — gains the merged-view + my_bulk surface (and pulls `app_config.types` re-exports along with it). - `api/adapters/app_config_fragment.py` — narrowed back to raw fragment ops only; merged-view methods no longer live here. - `api/gql/app_config_fragment/__init__.py` — emptied so `app_config.types` doesn't re-enter the partially-initialized package via the resolver. - `api/gql/app_config_fragment/resolver/__init__.py` — drops the removed `scoped_app_config_fragments` re-export. - `api/gql/app_config_fragment/types/{node,bulk_inputs}.py` — switches the `config` field type from `dict[str, Any]` to the `JSON` scalar so Strawberry can resolve it. - `api/gql/app_config/resolver/query.py` — calls the merged-view methods on `AppConfigAdapter` (not the fragment adapter). - `api/gql/data_loader/data_loaders.py` — drops the stale `batch_load_by_ids` reference on `AppConfigFragmentAdapter`. - `api/gql/schema.py` — imports resolver functions via the submodule paths directly, matching the lighter `app_config_fragment` package init. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent dae0ead commit d3191d2

8 files changed

Lines changed: 43 additions & 149 deletions

File tree

src/ai/backend/manager/api/adapters/app_config_fragment.py

Lines changed: 1 addition & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,13 @@
66

77
from __future__ import annotations
88

9-
from ai.backend.common.contexts.user import current_user
10-
from ai.backend.common.dto.manager.v2.app_config.request import (
11-
AppConfigFilter,
12-
AppConfigOrder,
13-
GetUserAppConfigInput,
14-
SearchAppConfigsInput,
15-
SearchMyAppConfigsInput,
16-
)
17-
from ai.backend.common.dto.manager.v2.app_config.response import (
18-
AppConfigNode,
19-
BulkCreateMyAppConfigFragmentsPayload,
20-
BulkUpdateMyAppConfigFragmentsPayload,
21-
GetUserAppConfigPayload,
22-
SearchAppConfigsPayload,
23-
)
24-
from ai.backend.common.dto.manager.v2.app_config.types import (
25-
AppConfigOrderField,
26-
)
279
from ai.backend.common.dto.manager.v2.app_config_fragment.request import (
2810
AdminBulkCreateAppConfigFragmentsInput,
2911
AdminBulkPurgeAppConfigFragmentsInput,
3012
AdminBulkUpdateAppConfigFragmentsInput,
3113
AppConfigFragmentFilter,
3214
AppConfigFragmentKeyInput,
3315
AppConfigFragmentOrder,
34-
BulkCreateMyAppConfigFragmentsInput,
35-
BulkUpdateMyAppConfigFragmentsInput,
3616
SearchAppConfigFragmentsInput,
3717
)
3818
from ai.backend.common.dto.manager.v2.app_config_fragment.response import (
@@ -53,11 +33,9 @@
5333
AppConfigScopeType as DTOAppConfigScopeType,
5434
)
5535
from ai.backend.manager.api.adapter_options.pagination.pagination import PaginationSpec
56-
from ai.backend.manager.data.app_config.types import AppConfigData
5736
from ai.backend.manager.data.app_config_fragment.bulk_types import (
5837
AppConfigFragmentBulkItem,
5938
AppConfigFragmentBulkItemError,
60-
MyAppConfigFragmentBulkItem,
6139
)
6240
from ai.backend.manager.data.app_config_fragment.types import (
6341
AppConfigFragmentData,
@@ -67,10 +45,7 @@
6745
from ai.backend.manager.models.app_config_fragment.conditions import AppConfigFragmentConditions
6846
from ai.backend.manager.models.app_config_fragment.orders import AppConfigFragmentOrders
6947
from ai.backend.manager.models.app_config_fragment.row import AppConfigFragmentRow
70-
from ai.backend.manager.repositories.app_config_fragment.types import (
71-
AppConfigFragmentSearchScope,
72-
UserAppConfigSearchScope,
73-
)
48+
from ai.backend.manager.repositories.app_config_fragment.types import AppConfigFragmentSearchScope
7449
from ai.backend.manager.repositories.base import BatchQuerier, QueryCondition, QueryOrder
7550
from ai.backend.manager.services.app_config_fragment.actions.admin_bulk_create import (
7651
AdminBulkCreateAppConfigFragmentsAction,
@@ -84,25 +59,10 @@
8459
from ai.backend.manager.services.app_config_fragment.actions.admin_search import (
8560
AdminSearchAppConfigFragmentsAction,
8661
)
87-
from ai.backend.manager.services.app_config_fragment.actions.admin_search_app_configs import (
88-
AdminSearchAppConfigsAction,
89-
)
90-
from ai.backend.manager.services.app_config_fragment.actions.bulk_create_my import (
91-
BulkCreateMyAppConfigFragmentsAction,
92-
)
93-
from ai.backend.manager.services.app_config_fragment.actions.bulk_update_my import (
94-
BulkUpdateMyAppConfigFragmentsAction,
95-
)
9662
from ai.backend.manager.services.app_config_fragment.actions.get import GetAppConfigFragmentAction
97-
from ai.backend.manager.services.app_config_fragment.actions.get_user_app_config import (
98-
GetUserAppConfigAction,
99-
)
10063
from ai.backend.manager.services.app_config_fragment.actions.search import (
10164
SearchAppConfigFragmentsAction,
10265
)
103-
from ai.backend.manager.services.app_config_fragment.actions.search_user_app_configs import (
104-
SearchUserAppConfigsAction,
105-
)
10666

10767
from .base import BaseAdapter
10868

@@ -330,52 +290,6 @@ async def admin_bulk_purge(
330290
failed=[self._bulk_error_to_dto(err) for err in result.failed],
331291
)
332292

333-
async def my_bulk_create(
334-
self, input: BulkCreateMyAppConfigFragmentsInput
335-
) -> BulkCreateMyAppConfigFragmentsPayload:
336-
me = current_user()
337-
if me is None:
338-
raise UnreachableError("User context is not available")
339-
items = [
340-
MyAppConfigFragmentBulkItem(name=item.name, extra_config=dict(item.extra_config))
341-
for item in input.items
342-
]
343-
wrapper = await self._processors.app_config_fragment.bulk_create_my.wait_for_complete(
344-
BulkCreateMyAppConfigFragmentsAction(
345-
entity_ids=[item.name for item in items],
346-
user_id=me.user_id,
347-
items=items,
348-
)
349-
)
350-
result = wrapper.result
351-
return BulkCreateMyAppConfigFragmentsPayload(
352-
created=[self._app_config_data_to_dto(item) for item in result.created],
353-
failed=[self._bulk_error_to_dto(err) for err in result.failed],
354-
)
355-
356-
async def my_bulk_update(
357-
self, input: BulkUpdateMyAppConfigFragmentsInput
358-
) -> BulkUpdateMyAppConfigFragmentsPayload:
359-
me = current_user()
360-
if me is None:
361-
raise UnreachableError("User context is not available")
362-
items = [
363-
MyAppConfigFragmentBulkItem(name=item.name, extra_config=dict(item.extra_config))
364-
for item in input.items
365-
]
366-
wrapper = await self._processors.app_config_fragment.bulk_update_my.wait_for_complete(
367-
BulkUpdateMyAppConfigFragmentsAction(
368-
entity_ids=[item.name for item in items],
369-
user_id=me.user_id,
370-
items=items,
371-
)
372-
)
373-
result = wrapper.result
374-
return BulkUpdateMyAppConfigFragmentsPayload(
375-
updated=[self._app_config_data_to_dto(item) for item in result.updated],
376-
failed=[self._bulk_error_to_dto(err) for err in result.failed],
377-
)
378-
379293
@staticmethod
380294
def _bulk_error_to_dto(
381295
err: AppConfigFragmentBulkItemError,

src/ai/backend/manager/api/gql/app_config/resolver/query.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ async def my_app_configs(
5151
limit: int | None = None,
5252
offset: int | None = None,
5353
) -> list[AppConfigGQL]:
54-
payload = await info.context.adapters.app_config_fragment.my_search_app_configs(
54+
payload = await info.context.adapters.app_config.my_search_app_configs(
5555
SearchMyAppConfigsInput(
5656
filter=filter.to_pydantic() if filter else None,
5757
order=[o.to_pydantic() for o in order_by] if order_by else None,
@@ -88,7 +88,7 @@ async def admin_app_configs(
8888
offset: int | None = None,
8989
) -> list[AppConfigGQL]:
9090
check_admin_only()
91-
payload = await info.context.adapters.app_config_fragment.admin_search_app_configs(
91+
payload = await info.context.adapters.app_config.admin_search_app_configs(
9292
SearchAppConfigsInput(
9393
filter=filter.to_pydantic() if filter else None,
9494
order=[o.to_pydantic() for o in order_by] if order_by else None,
Lines changed: 8 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,9 @@
1-
"""AppConfigFragment GraphQL API package."""
1+
"""AppConfigFragment GraphQL API package.
22
3-
from .resolver import (
4-
admin_app_config_fragments,
5-
admin_bulk_create_app_config_fragments,
6-
admin_bulk_purge_app_config_fragments,
7-
admin_bulk_update_app_config_fragments,
8-
app_config_fragment,
9-
my_bulk_create_app_config_fragments,
10-
my_bulk_update_app_config_fragments,
11-
)
12-
from .types import (
13-
AppConfigFragmentFilterGQL,
14-
AppConfigFragmentGQL,
15-
AppConfigFragmentKeyInputGQL,
16-
AppConfigFragmentOrderByGQL,
17-
AppConfigFragmentOrderFieldGQL,
18-
AppConfigScopeTypeGQL,
19-
)
20-
21-
__all__ = [
22-
# Queries — scope-bound list belongs on DomainV2 / UserV2 child fields
23-
"app_config_fragment",
24-
"scoped_app_config_fragments",
25-
"admin_app_config_fragments",
26-
# Bulk mutations
27-
"admin_bulk_create_app_config_fragments",
28-
"admin_bulk_update_app_config_fragments",
29-
"admin_bulk_purge_app_config_fragments",
30-
"my_bulk_create_app_config_fragments",
31-
"my_bulk_update_app_config_fragments",
32-
# Types
33-
"AppConfigFragmentGQL",
34-
"AppConfigScopeTypeGQL",
35-
"AppConfigFragmentFilterGQL",
36-
"AppConfigFragmentOrderByGQL",
37-
"AppConfigFragmentOrderFieldGQL",
38-
"AppConfigFragmentKeyInputGQL",
39-
]
3+
Resolver and type names are re-exported by ``schema.py`` directly via
4+
their submodules to keep this package's ``__init__`` import-light: a
5+
top-level ``from app_config_fragment import ...`` would otherwise drag
6+
in the mutation resolvers, which back-import from
7+
``app_config.types.bulk_payloads`` and form an import cycle when
8+
``AppConfigGQL`` is loading.
9+
"""

src/ai/backend/manager/api/gql/app_config_fragment/resolver/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from .query import (
99
admin_app_config_fragments,
1010
app_config_fragment,
11-
scoped_app_config_fragments,
1211
)
1312

1413
__all__ = [

src/ai/backend/manager/api/gql/app_config_fragment/types/bulk_inputs.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from __future__ import annotations
44

5-
from typing import Any
5+
from strawberry.scalars import JSON
66

77
from ai.backend.common.dto.manager.v2.app_config_fragment.request import (
88
AdminAppConfigFragmentItemInput as AdminItemInputDTO,
@@ -46,7 +46,7 @@
4646
)
4747
class AdminAppConfigFragmentItemInputGQL(PydanticInputMixin[AdminItemInputDTO]):
4848
key: AppConfigFragmentKeyInputGQL = gql_field(description="Natural-key identifier.")
49-
config: dict[str, Any] = gql_field(description="Raw configuration payload.")
49+
config: JSON = gql_field(description="Raw configuration payload.")
5050

5151

5252
@gql_pydantic_input(
@@ -91,7 +91,7 @@ class AdminBulkPurgeAppConfigFragmentInputGQL(PydanticInputMixin[AdminBulkPurgeI
9191
)
9292
class MyAppConfigFragmentItemInputGQL(PydanticInputMixin[MyItemInputDTO]):
9393
name: str = gql_field(description="Policy name.")
94-
config: dict[str, Any] = gql_field(description="Raw configuration payload.")
94+
config: JSON = gql_field(description="Raw configuration payload.")
9595

9696

9797
@gql_pydantic_input(

src/ai/backend/manager/api/gql/app_config_fragment/types/node.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,26 @@
33
from __future__ import annotations
44

55
from datetime import datetime
6-
from typing import Any
76
from uuid import UUID
87

8+
from strawberry.scalars import JSON
9+
910
from ai.backend.common.dto.manager.v2.app_config_fragment.response import AppConfigFragmentNode
1011
from ai.backend.common.dto.manager.v2.app_config_fragment.types import AppConfigScopeType
1112
from ai.backend.common.meta.meta import NEXT_RELEASE_VERSION
1213
from ai.backend.manager.api.gql.decorators import (
1314
BackendAIGQLMeta,
14-
gql_enum,
1515
gql_field,
1616
gql_pydantic_type,
1717
)
1818
from ai.backend.manager.api.gql.pydantic_compat import PydanticOutputMixin
1919

20-
# Register the shared DTO enum as a Strawberry type.
21-
AppConfigScopeTypeGQL = gql_enum(
22-
BackendAIGQLMeta(
23-
added_version=NEXT_RELEASE_VERSION,
24-
description="App-config scope type.",
25-
),
26-
AppConfigScopeType,
27-
name="AppConfigScopeType",
28-
)
20+
# The shared DTO enum is auto-registered by Strawberry the first time it
21+
# is referenced as a typed field. Re-export under the ``GQL`` suffix so
22+
# other modules can write `from ... import AppConfigScopeTypeGQL`. Calling
23+
# `strawberry.enum(...)` here would clash with that auto-registration
24+
# under the same `"AppConfigScopeType"` name.
25+
AppConfigScopeTypeGQL = AppConfigScopeType
2926

3027

3128
@gql_pydantic_type(
@@ -41,6 +38,6 @@ class AppConfigFragmentGQL(PydanticOutputMixin[AppConfigFragmentNode]):
4138
scope_type: AppConfigScopeType = gql_field(description="Scope type.")
4239
scope_id: str = gql_field(description="Scope id.")
4340
name: str = gql_field(description="Policy name (FK to app_config_policies).")
44-
config: dict[str, Any] | None = gql_field(description="Raw configuration payload, or null.")
41+
config: JSON | None = gql_field(description="Raw configuration payload, or null.")
4542
created_at: datetime = gql_field(description="Creation timestamp.")
4643
updated_at: datetime | None = gql_field(description="Last update timestamp.")

src/ai/backend/manager/api/gql/data_loader/data_loaders.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,12 +123,25 @@ def app_config_fragment_loader(
123123
adapter = self._adapters.app_config_fragment
124124

125125
async def load_fn(ids: list[uuid.UUID]) -> list[AppConfigFragmentGQL | None]:
126+
from ai.backend.common.dto.manager.query import UUIDFilter # pants: no-infer-dep
127+
from ai.backend.common.dto.manager.v2.app_config_fragment.request import ( # pants: no-infer-dep
128+
AppConfigFragmentFilter,
129+
SearchAppConfigFragmentsInput,
130+
)
126131
from ai.backend.manager.api.gql.app_config_fragment.types import ( # pants: no-infer-dep
127132
AppConfigFragmentGQL as F,
128133
)
129134

130-
dtos = await adapter.batch_load_by_ids(ids)
131-
return [F.from_pydantic(dto) if dto is not None else None for dto in dtos]
135+
if not ids:
136+
return []
137+
payload = await adapter.admin_search(
138+
SearchAppConfigFragmentsInput(
139+
filter=AppConfigFragmentFilter(id=UUIDFilter.model_validate({"in": list(ids)})),
140+
limit=len(ids),
141+
),
142+
)
143+
by_id = {dto.id: F.from_pydantic(dto) for dto in payload.items}
144+
return [by_id.get(fragment_id) for fragment_id in ids]
132145

133146
return DataLoader(load_fn=load_fn)
134147

src/ai/backend/manager/api/gql/schema.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,17 @@
2020
my_app_configs,
2121
public_app_config_fragments,
2222
)
23-
from .app_config_fragment import (
24-
admin_app_config_fragments,
23+
from .app_config_fragment.resolver.mutation import (
2524
admin_bulk_create_app_config_fragments,
2625
admin_bulk_purge_app_config_fragments,
2726
admin_bulk_update_app_config_fragments,
28-
app_config_fragment,
2927
my_bulk_create_app_config_fragments,
3028
my_bulk_update_app_config_fragments,
3129
)
30+
from .app_config_fragment.resolver.query import (
31+
admin_app_config_fragments,
32+
app_config_fragment,
33+
)
3234
from .app_config_policy import (
3335
admin_bulk_create_app_config_policies,
3436
admin_bulk_purge_app_config_policies,
@@ -531,7 +533,6 @@ class Query:
531533
app_config_policies = app_config_policies
532534
# App Config Fragment APIs
533535
app_config_fragment = app_config_fragment
534-
scoped_app_config_fragments = scoped_app_config_fragments
535536
admin_app_config_fragments = admin_app_config_fragments
536537
public_app_config_fragments = public_app_config_fragments
537538
# App Config merged view APIs

0 commit comments

Comments
 (0)