Skip to content

Commit 14f9a7c

Browse files
pcrespovmrnicegyu11
authored andcommitted
✨ web-api: Adds endpoint to retrieve project services with Release and Access Information (ITISFoundation#7287)
1 parent dde5f6e commit 14f9a7c

File tree

32 files changed

+1174
-235
lines changed

32 files changed

+1174
-235
lines changed

api/specs/web-server/_catalog.py

+25-39
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from models_library.api_schemas_api_server.pricing_plans import ServicePricingPlanGet
55
from models_library.api_schemas_webserver.catalog import (
66
CatalogServiceGet,
7+
CatalogServiceListItem,
78
CatalogServiceUpdate,
89
ServiceInputGet,
910
ServiceInputKey,
@@ -31,108 +32,94 @@
3132
)
3233

3334

34-
#
35-
# /catalog/services/* COLLECTION
36-
#
37-
38-
3935
@router.get(
4036
"/catalog/services/-/latest",
41-
response_model=Page[CatalogServiceGet],
37+
response_model=Page[CatalogServiceListItem],
4238
)
43-
def list_services_latest(_query_params: Annotated[ListServiceParams, Depends()]):
39+
def list_services_latest(_query: Annotated[ListServiceParams, Depends()]):
4440
pass
4541

4642

4743
@router.get(
4844
"/catalog/services/{service_key}/{service_version}",
4945
response_model=Envelope[CatalogServiceGet],
5046
)
51-
def get_service(_path_params: Annotated[ServicePathParams, Depends()]):
52-
...
47+
def get_service(_path: Annotated[ServicePathParams, Depends()]): ...
5348

5449

5550
@router.patch(
5651
"/catalog/services/{service_key}/{service_version}",
5752
response_model=Envelope[CatalogServiceGet],
5853
)
5954
def update_service(
60-
_path_params: Annotated[ServicePathParams, Depends()],
61-
_update: CatalogServiceUpdate,
62-
):
63-
...
55+
_path: Annotated[ServicePathParams, Depends()],
56+
_body: CatalogServiceUpdate,
57+
): ...
6458

6559

6660
@router.get(
6761
"/catalog/services/{service_key}/{service_version}/inputs",
6862
response_model=Envelope[list[ServiceInputGet]],
6963
)
7064
def list_service_inputs(
71-
_path_params: Annotated[ServicePathParams, Depends()],
72-
):
73-
...
65+
_path: Annotated[ServicePathParams, Depends()],
66+
): ...
7467

7568

7669
@router.get(
7770
"/catalog/services/{service_key}/{service_version}/inputs/{input_key}",
7871
response_model=Envelope[ServiceInputGet],
7972
)
8073
def get_service_input(
81-
_path_params: Annotated[_ServiceInputsPathParams, Depends()],
82-
):
83-
...
74+
_path: Annotated[_ServiceInputsPathParams, Depends()],
75+
): ...
8476

8577

8678
@router.get(
8779
"/catalog/services/{service_key}/{service_version}/inputs:match",
8880
response_model=Envelope[list[ServiceInputKey]],
8981
)
9082
def get_compatible_inputs_given_source_output(
91-
_path_params: Annotated[ServicePathParams, Depends()],
92-
_query_params: Annotated[_FromServiceOutputParams, Depends()],
93-
):
94-
...
83+
_path: Annotated[ServicePathParams, Depends()],
84+
_query: Annotated[_FromServiceOutputParams, Depends()],
85+
): ...
9586

9687

9788
@router.get(
9889
"/catalog/services/{service_key}/{service_version}/outputs",
9990
response_model=Envelope[list[ServiceOutputKey]],
10091
)
10192
def list_service_outputs(
102-
_path_params: Annotated[ServicePathParams, Depends()],
103-
):
104-
...
93+
_path: Annotated[ServicePathParams, Depends()],
94+
): ...
10595

10696

10797
@router.get(
10898
"/catalog/services/{service_key}/{service_version}/outputs/{output_key}",
10999
response_model=Envelope[list[ServiceOutputGet]],
110100
)
111101
def get_service_output(
112-
_path_params: Annotated[_ServiceOutputsPathParams, Depends()],
113-
):
114-
...
102+
_path: Annotated[_ServiceOutputsPathParams, Depends()],
103+
): ...
115104

116105

117106
@router.get(
118107
"/catalog/services/{service_key}/{service_version}/outputs:match",
119108
response_model=Envelope[list[ServiceOutputKey]],
120109
)
121110
def get_compatible_outputs_given_target_input(
122-
_path_params: Annotated[ServicePathParams, Depends()],
123-
_query_params: Annotated[_ToServiceInputsParams, Depends()],
124-
):
125-
...
111+
_path: Annotated[ServicePathParams, Depends()],
112+
_query: Annotated[_ToServiceInputsParams, Depends()],
113+
): ...
126114

127115

128116
@router.get(
129117
"/catalog/services/{service_key}/{service_version}/resources",
130118
response_model=Envelope[ServiceResourcesGet],
131119
)
132120
def get_service_resources(
133-
_params: Annotated[ServicePathParams, Depends()],
134-
):
135-
...
121+
_path: Annotated[ServicePathParams, Depends()],
122+
): ...
136123

137124

138125
@router.get(
@@ -142,6 +129,5 @@ def get_service_resources(
142129
tags=["pricing-plans"],
143130
)
144131
async def get_service_pricing_plan(
145-
_params: Annotated[ServicePathParams, Depends()],
146-
):
147-
...
132+
_path: Annotated[ServicePathParams, Depends()],
133+
): ...

api/specs/web-server/_projects_nodes.py

+12-8
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
NodePatch,
1919
NodeRetrieve,
2020
NodeRetrieved,
21+
ProjectNodeServicesGet,
2122
ServiceResourcesDict,
2223
)
2324
from models_library.generics import Envelope
@@ -76,8 +77,7 @@ def delete_node(project_id: str, node_id: str): # noqa: ARG001
7677
)
7778
def retrieve_node(
7879
project_id: str, node_id: str, _retrieve: NodeRetrieve # noqa: ARG001
79-
):
80-
...
80+
): ...
8181

8282

8383
@router.post(
@@ -147,24 +147,29 @@ def get_node_resources(project_id: str, node_id: str): # noqa: ARG001
147147
)
148148
def replace_node_resources(
149149
project_id: str, node_id: str, _new: ServiceResourcesDict # noqa: ARG001
150-
):
151-
...
150+
): ...
152151

153152

154153
#
155154
# projects/*/nodes/-/services
156155
#
157156

158157

158+
@router.get(
159+
"/projects/{project_id}/nodes/-/services",
160+
response_model=Envelope[ProjectNodeServicesGet],
161+
)
162+
async def get_project_services(project_id: ProjectID): ...
163+
164+
159165
@router.get(
160166
"/projects/{project_id}/nodes/-/services:access",
161167
response_model=Envelope[_ProjectGroupAccess],
162168
description="Check whether provided group has access to the project services",
163169
)
164170
async def get_project_services_access_for_gid(
165171
project_id: ProjectID, for_gid: GroupID # noqa: ARG001
166-
):
167-
...
172+
): ...
168173

169174

170175
assert_handler_signature_against_model(
@@ -197,8 +202,7 @@ async def list_project_nodes_previews(project_id: ProjectID): # noqa: ARG001
197202
)
198203
async def get_project_node_preview(
199204
project_id: ProjectID, node_id: NodeID # noqa: ARG001
200-
):
201-
...
205+
): ...
202206

203207

204208
assert_handler_signature_against_model(get_project_node_preview, NodePathParams)
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
1+
from typing import Annotated
2+
13
from pydantic import BaseModel, ConfigDict, Field
24

35

46
class AccessRights(BaseModel):
5-
read: bool = Field(..., description="has read access")
6-
write: bool = Field(..., description="has write access")
7-
delete: bool = Field(..., description="has deletion rights")
7+
read: Annotated[bool, Field(description="has read access")]
8+
write: Annotated[bool, Field(description="has write access")]
9+
delete: Annotated[bool, Field(description="has deletion rights")]
10+
11+
model_config = ConfigDict(extra="forbid")
12+
13+
14+
class ExecutableAccessRights(BaseModel):
15+
write: Annotated[bool, Field(description="can change executable settings")]
16+
execute: Annotated[bool, Field(description="can run executable")]
817

918
model_config = ConfigDict(extra="forbid")

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

+51-19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from datetime import datetime
2-
from typing import Any, TypeAlias
2+
from typing import Annotated, Any, TypeAlias
33

4+
from common_library.basic_types import DEFAULT_FACTORY
45
from models_library.rpc_pagination import PageRpc
56
from pydantic import ConfigDict, Field, HttpUrl, NonNegativeInt
67
from pydantic.config import JsonDict
@@ -154,9 +155,10 @@
154155
class ServiceGet(
155156
ServiceMetaDataPublished, ServiceAccessRights, ServiceMetaDataEditable
156157
): # pylint: disable=too-many-ancestors
157-
owner: LowerCaseEmailStr | None = Field(
158-
description="None when the owner email cannot be found in the database"
159-
)
158+
owner: Annotated[
159+
LowerCaseEmailStr | None,
160+
Field(description="None when the owner email cannot be found in the database"),
161+
]
160162

161163
@staticmethod
162164
def _update_json_schema_extra(schema: JsonDict) -> None:
@@ -169,7 +171,7 @@ def _update_json_schema_extra(schema: JsonDict) -> None:
169171
)
170172

171173

172-
class ServiceGetV2(CatalogOutputSchema):
174+
class _BaseServiceGetV2(CatalogOutputSchema):
173175
# Model used in catalog's rpc and rest interfaces
174176
key: ServiceKey
175177
version: ServiceVersion
@@ -183,13 +185,14 @@ class ServiceGetV2(CatalogOutputSchema):
183185

184186
version_display: str | None = None
185187

186-
service_type: ServiceType = Field(default=..., alias="type")
188+
service_type: Annotated[ServiceType, Field(alias="type")]
187189

188190
contact: LowerCaseEmailStr | None
189-
authors: list[Author] = Field(..., min_length=1)
190-
owner: LowerCaseEmailStr | None = Field(
191-
description="None when the owner email cannot be found in the database"
192-
)
191+
authors: Annotated[list[Author], Field(min_length=1)]
192+
owner: Annotated[
193+
LowerCaseEmailStr | None,
194+
Field(description="None when the owner email cannot be found in the database"),
195+
]
193196

194197
inputs: ServiceInputsDict
195198
outputs: ServiceOutputsDict
@@ -202,13 +205,25 @@ class ServiceGetV2(CatalogOutputSchema):
202205
classifiers: list[str] | None = []
203206
quality: dict[str, Any] = {}
204207

205-
history: list[ServiceRelease] = Field(
206-
default_factory=list,
207-
description="history of releases for this service at this point in time, starting from the newest to the oldest."
208-
" It includes current release.",
209-
json_schema_extra={"default": []},
208+
model_config = ConfigDict(
209+
extra="forbid",
210+
populate_by_name=True,
211+
alias_generator=snake_to_camel,
210212
)
211213

214+
215+
class ServiceGetV2(_BaseServiceGetV2):
216+
# Model used in catalog's rpc and rest interfaces
217+
history: Annotated[
218+
list[ServiceRelease],
219+
Field(
220+
default_factory=list,
221+
description="history of releases for this service at this point in time, starting from the newest to the oldest."
222+
" It includes current release.",
223+
json_schema_extra={"default": []},
224+
),
225+
] = DEFAULT_FACTORY
226+
212227
@staticmethod
213228
def _update_json_schema_extra(schema: JsonDict) -> None:
214229
schema.update(
@@ -269,16 +284,25 @@ def _update_json_schema_extra(schema: JsonDict) -> None:
269284
)
270285

271286
model_config = ConfigDict(
272-
extra="forbid",
273-
populate_by_name=True,
274-
alias_generator=snake_to_camel,
275287
json_schema_extra=_update_json_schema_extra,
276288
)
277289

278290

291+
class ServiceListItem(_BaseServiceGetV2):
292+
history: Annotated[
293+
list[ServiceRelease],
294+
Field(
295+
default_factory=list,
296+
deprecated=True,
297+
description="History will be replaced by current 'release' instead",
298+
json_schema_extra={"default": []},
299+
),
300+
] = DEFAULT_FACTORY
301+
302+
279303
PageRpcServicesGetV2: TypeAlias = PageRpc[
280304
# WARNING: keep this definition in models_library and not in the RPC interface
281-
ServiceGetV2
305+
ServiceListItem
282306
]
283307

284308
ServiceResourcesGet: TypeAlias = ServiceResourcesDict
@@ -310,3 +334,11 @@ class ServiceUpdateV2(CatalogInputSchema):
310334
assert set(ServiceUpdateV2.model_fields.keys()) - set( # nosec
311335
ServiceGetV2.model_fields.keys()
312336
) == {"deprecated"}
337+
338+
339+
class MyServiceGet(CatalogOutputSchema):
340+
key: ServiceKey
341+
release: ServiceRelease
342+
343+
owner: GroupID | None
344+
my_access_rights: ServiceGroupAccessRightsV2

0 commit comments

Comments
 (0)