Skip to content

Commit c535784

Browse files
authored
✨ New CatalogService in api-server that connects via rpc to the catalog micro-service (#7439)
1 parent 0d58ad8 commit c535784

File tree

19 files changed

+807
-135
lines changed

19 files changed

+807
-135
lines changed

packages/models-library/src/models_library/api_schemas_catalog/services.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,11 +323,20 @@ def _update_json_schema_extra(schema: JsonDict) -> None:
323323
)
324324

325325

326-
PageRpcServicesGetV2: TypeAlias = PageRpc[
326+
PageRpcLatestServiceGet: TypeAlias = PageRpc[
327327
# WARNING: keep this definition in models_library and not in the RPC interface
328+
# otherwise the metaclass PageRpc[*] will create *different* classes in server/client side
329+
# and will fail to serialize/deserialize these parameters when transmitted/received
328330
LatestServiceGet
329331
]
330332

333+
PageRpcServiceRelease: TypeAlias = PageRpc[
334+
# WARNING: keep this definition in models_library and not in the RPC interface
335+
# otherwise the metaclass PageRpc[*] will create *different* classes in server/client side
336+
# and will fail to serialize/deserialize these parameters when transmitted/received
337+
ServiceRelease
338+
]
339+
331340
ServiceResourcesGet: TypeAlias = ServiceResourcesDict
332341

333342

@@ -365,3 +374,6 @@ class MyServiceGet(CatalogOutputSchema):
365374

366375
owner: GroupID | None
367376
my_access_rights: ServiceGroupAccessRightsV2
377+
378+
379+
__all__: tuple[str, ...] = ("ServiceRelease",)

packages/models-library/src/models_library/rest_pagination.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828

2929
PageOffsetInt: TypeAlias = NonNegativeInt
3030

31+
PageTotalCount: TypeAlias = NonNegativeInt
32+
3133
DEFAULT_NUMBER_OF_ITEMS_PER_PAGE: Final[PageLimitInt] = TypeAdapter(
3234
PageLimitInt
3335
).validate_python(20)
@@ -70,7 +72,7 @@ class PageQueryParameters(RequestParameters):
7072

7173
class PageMetaInfoLimitOffset(BaseModel):
7274
limit: PositiveInt = DEFAULT_NUMBER_OF_ITEMS_PER_PAGE
73-
total: NonNegativeInt
75+
total: PageTotalCount
7476
offset: NonNegativeInt = 0
7577
count: NonNegativeInt
7678

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# pylint: disable=not-context-manager
2+
# pylint: disable=protected-access
3+
# pylint: disable=redefined-outer-name
4+
# pylint: disable=unused-argument
5+
# pylint: disable=unused-variable
6+
7+
8+
from models_library.api_schemas_catalog.services import LatestServiceGet, ServiceGetV2
9+
from models_library.api_schemas_webserver.catalog import (
10+
CatalogServiceUpdate,
11+
)
12+
from models_library.products import ProductName
13+
from models_library.rest_pagination import PageOffsetInt
14+
from models_library.rpc_pagination import PageLimitInt, PageRpc
15+
from models_library.services_history import ServiceRelease
16+
from models_library.services_types import ServiceKey, ServiceVersion
17+
from models_library.users import UserID
18+
from pydantic import NonNegativeInt, TypeAdapter
19+
from servicelib.rabbitmq._client_rpc import RabbitMQRPCClient
20+
21+
22+
class CatalogRpcSideEffects:
23+
# pylint: disable=no-self-use
24+
async def list_services_paginated(
25+
self,
26+
rpc_client: RabbitMQRPCClient,
27+
*,
28+
product_name: ProductName,
29+
user_id: UserID,
30+
limit: PageLimitInt,
31+
offset: NonNegativeInt,
32+
):
33+
assert rpc_client
34+
assert product_name
35+
assert user_id
36+
37+
items = TypeAdapter(list[LatestServiceGet]).validate_python(
38+
LatestServiceGet.model_json_schema()["examples"],
39+
)
40+
total_count = len(items)
41+
42+
return PageRpc[LatestServiceGet].create(
43+
items[offset : offset + limit],
44+
total=total_count,
45+
limit=limit,
46+
offset=offset,
47+
)
48+
49+
async def get_service(
50+
self,
51+
rpc_client: RabbitMQRPCClient,
52+
*,
53+
product_name: ProductName,
54+
user_id: UserID,
55+
service_key: ServiceKey,
56+
service_version: ServiceVersion,
57+
):
58+
assert rpc_client
59+
assert product_name
60+
assert user_id
61+
62+
got = ServiceGetV2.model_validate(
63+
ServiceGetV2.model_json_schema()["examples"][0]
64+
)
65+
got.version = service_version
66+
got.key = service_key
67+
68+
return got
69+
70+
async def update_service(
71+
self,
72+
rpc_client: RabbitMQRPCClient,
73+
*,
74+
product_name: ProductName,
75+
user_id: UserID,
76+
service_key: ServiceKey,
77+
service_version: ServiceVersion,
78+
update: CatalogServiceUpdate,
79+
):
80+
assert rpc_client
81+
assert product_name
82+
assert user_id
83+
84+
got = ServiceGetV2.model_validate(
85+
ServiceGetV2.model_json_schema()["examples"][0]
86+
)
87+
got.version = service_version
88+
got.key = service_key
89+
return got.model_copy(update=update.model_dump(exclude_unset=True))
90+
91+
async def list_my_service_history_paginated(
92+
self,
93+
rpc_client: RabbitMQRPCClient,
94+
*,
95+
product_name: ProductName,
96+
user_id: UserID,
97+
service_key: ServiceKey,
98+
offset: PageOffsetInt,
99+
limit: PageLimitInt,
100+
) -> PageRpc[ServiceRelease]:
101+
102+
assert rpc_client
103+
assert product_name
104+
assert user_id
105+
assert service_key
106+
107+
items = TypeAdapter(list[ServiceRelease]).validate_python(
108+
ServiceRelease.model_json_schema()["examples"],
109+
)
110+
total_count = len(items)
111+
112+
return PageRpc[ServiceRelease].create(
113+
items[offset : offset + limit],
114+
total=total_count,
115+
limit=limit,
116+
offset=offset,
117+
)

packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/catalog/services.py

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,23 @@
77
from models_library.api_schemas_catalog.services import (
88
LatestServiceGet,
99
MyServiceGet,
10+
PageRpcLatestServiceGet,
11+
PageRpcServiceRelease,
1012
ServiceGetV2,
13+
ServiceRelease,
1114
ServiceUpdateV2,
1215
)
1316
from models_library.products import ProductName
1417
from models_library.rabbitmq_basic_types import RPCMethodName
18+
from models_library.rest_pagination import PageOffsetInt
1519
from models_library.rpc_pagination import (
1620
DEFAULT_NUMBER_OF_ITEMS_PER_PAGE,
1721
PageLimitInt,
1822
PageRpc,
1923
)
2024
from models_library.services_types import ServiceKey, ServiceVersion
2125
from models_library.users import UserID
22-
from pydantic import NonNegativeInt, TypeAdapter, validate_call
26+
from pydantic import TypeAdapter, validate_call
2327
from servicelib.logging_utils import log_decorator
2428
from servicelib.rabbitmq._constants import RPC_REQUEST_DEFAULT_TIMEOUT_S
2529

@@ -34,8 +38,8 @@ async def list_services_paginated( # pylint: disable=too-many-arguments
3438
product_name: ProductName,
3539
user_id: UserID,
3640
limit: PageLimitInt = DEFAULT_NUMBER_OF_ITEMS_PER_PAGE,
37-
offset: NonNegativeInt = 0,
38-
) -> PageRpc[LatestServiceGet]:
41+
offset: PageOffsetInt = 0,
42+
) -> PageRpcLatestServiceGet:
3943
"""
4044
Raises:
4145
ValidationError: on invalid arguments
@@ -47,7 +51,7 @@ async def _call(
4751
product_name: ProductName,
4852
user_id: UserID,
4953
limit: PageLimitInt,
50-
offset: NonNegativeInt,
54+
offset: PageOffsetInt,
5155
):
5256
return await rpc_client.request(
5357
CATALOG_RPC_NAMESPACE,
@@ -235,3 +239,51 @@ async def _call(
235239
result = await _call(product_name=product_name, user_id=user_id, ids=ids)
236240
assert TypeAdapter(list[MyServiceGet]).validate_python(result) is not None # nosec
237241
return cast(list[MyServiceGet], result)
242+
243+
244+
async def list_my_service_history_paginated( # pylint: disable=too-many-arguments
245+
rpc_client: RabbitMQRPCClient,
246+
*,
247+
product_name: ProductName,
248+
user_id: UserID,
249+
service_key: ServiceKey,
250+
limit: PageLimitInt = DEFAULT_NUMBER_OF_ITEMS_PER_PAGE,
251+
offset: PageOffsetInt = 0,
252+
) -> PageRpcServiceRelease:
253+
"""
254+
Raises:
255+
ValidationError: on invalid arguments
256+
"""
257+
258+
@validate_call()
259+
async def _call(
260+
product_name: ProductName,
261+
user_id: UserID,
262+
service_key: ServiceKey,
263+
limit: PageLimitInt,
264+
offset: PageOffsetInt,
265+
):
266+
return await rpc_client.request(
267+
CATALOG_RPC_NAMESPACE,
268+
TypeAdapter(RPCMethodName).validate_python(
269+
"list_my_service_history_paginated"
270+
),
271+
product_name=product_name,
272+
user_id=user_id,
273+
service_key=service_key,
274+
limit=limit,
275+
offset=offset,
276+
)
277+
278+
result = await _call(
279+
product_name=product_name,
280+
user_id=user_id,
281+
service_key=service_key,
282+
limit=limit,
283+
offset=offset,
284+
)
285+
286+
assert ( # nosec
287+
TypeAdapter(PageRpcServiceRelease).validate_python(result) is not None
288+
)
289+
return cast(PageRpc[ServiceRelease], result)

services/api-server/src/simcore_service_api_server/services_http/catalog.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99

1010
from fastapi import FastAPI, status
1111
from models_library.emails import LowerCaseEmailStr
12+
from models_library.products import ProductName
1213
from models_library.services import ServiceMetaDataPublished, ServiceType
14+
from models_library.users import UserID
1315
from pydantic import ConfigDict, TypeAdapter, ValidationError
1416
from settings_library.catalog import CatalogSettings
1517
from settings_library.tracing import TracingSettings
@@ -70,9 +72,9 @@ def to_solver(self) -> Solver:
7072

7173
_exception_mapper = partial(service_exception_mapper, service_name="Catalog")
7274

73-
TruncatedCatalogServiceOutAdapter: Final[
74-
TypeAdapter[TruncatedCatalogServiceOut]
75-
] = TypeAdapter(TruncatedCatalogServiceOut)
75+
TruncatedCatalogServiceOutAdapter: Final[TypeAdapter[TruncatedCatalogServiceOut]] = (
76+
TypeAdapter(TruncatedCatalogServiceOut)
77+
)
7678
TruncatedCatalogServiceOutListAdapter: Final[
7779
TypeAdapter[list[TruncatedCatalogServiceOut]]
7880
] = TypeAdapter(list[TruncatedCatalogServiceOut])
@@ -97,8 +99,8 @@ class CatalogApi(BaseServiceClientApi):
9799
async def list_solvers(
98100
self,
99101
*,
100-
user_id: int,
101-
product_name: str,
102+
user_id: UserID,
103+
product_name: ProductName,
102104
predicate: Callable[[Solver], bool] | None = None,
103105
) -> list[Solver]:
104106

@@ -140,7 +142,12 @@ async def list_solvers(
140142
http_status_map={status.HTTP_404_NOT_FOUND: SolverOrStudyNotFoundError}
141143
)
142144
async def get_service(
143-
self, *, user_id: int, name: SolverKeyId, version: VersionStr, product_name: str
145+
self,
146+
*,
147+
user_id: UserID,
148+
name: SolverKeyId,
149+
version: VersionStr,
150+
product_name: ProductName,
144151
) -> Solver:
145152

146153
assert version != LATEST_VERSION # nosec
@@ -171,8 +178,13 @@ async def get_service(
171178
http_status_map={status.HTTP_404_NOT_FOUND: SolverOrStudyNotFoundError}
172179
)
173180
async def get_service_ports(
174-
self, *, user_id: int, name: SolverKeyId, version: VersionStr, product_name: str
175-
):
181+
self,
182+
*,
183+
user_id: UserID,
184+
name: SolverKeyId,
185+
version: VersionStr,
186+
product_name: ProductName,
187+
) -> list[SolverPort]:
176188

177189
assert version != LATEST_VERSION # nosec
178190

@@ -190,7 +202,7 @@ async def get_service_ports(
190202
return TypeAdapter(list[SolverPort]).validate_python(response.json())
191203

192204
async def list_latest_releases(
193-
self, *, user_id: int, product_name: str
205+
self, *, user_id: UserID, product_name: ProductName
194206
) -> list[Solver]:
195207
solvers: list[Solver] = await self.list_solvers(
196208
user_id=user_id, product_name=product_name
@@ -205,7 +217,7 @@ async def list_latest_releases(
205217
return list(latest_releases.values())
206218

207219
async def list_solver_releases(
208-
self, *, user_id: int, solver_key: SolverKeyId, product_name: str
220+
self, *, user_id: UserID, solver_key: SolverKeyId, product_name: ProductName
209221
) -> list[Solver]:
210222
def _this_solver(solver: Solver) -> bool:
211223
return solver.id == solver_key
@@ -216,7 +228,7 @@ def _this_solver(solver: Solver) -> bool:
216228
return releases
217229

218230
async def get_latest_release(
219-
self, *, user_id: int, solver_key: SolverKeyId, product_name: str
231+
self, *, user_id: UserID, solver_key: SolverKeyId, product_name: ProductName
220232
) -> Solver:
221233
releases = await self.list_solver_releases(
222234
user_id=user_id, solver_key=solver_key, product_name=product_name

0 commit comments

Comments
 (0)