Skip to content

Commit 427d751

Browse files
♻️ Refactor catalog domain in webserver (#7308)
1 parent a4a3bc3 commit 427d751

39 files changed

+576
-278
lines changed

api/specs/web-server/_catalog.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from models_library.generics import Envelope
1616
from models_library.rest_pagination import Page
1717
from simcore_service_webserver._meta import API_VTAG
18-
from simcore_service_webserver.catalog._handlers import (
18+
from simcore_service_webserver.catalog._rest_controller import (
1919
ListServiceParams,
2020
ServicePathParams,
2121
_FromServiceOutputParams,

api/specs/web-server/_catalog_tags.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from models_library.api_schemas_webserver.catalog import CatalogServiceGet
1111
from models_library.generics import Envelope
1212
from simcore_service_webserver._meta import API_VTAG
13-
from simcore_service_webserver.catalog._tags_handlers import (
13+
from simcore_service_webserver.catalog._rest_tags_controller import (
1414
ServicePathParams,
1515
ServiceTagPathParams,
1616
)
@@ -31,8 +31,7 @@
3131
)
3232
def list_service_tags(
3333
_path_params: Annotated[ServicePathParams, Depends()],
34-
):
35-
...
34+
): ...
3635

3736

3837
@router.post(
@@ -41,8 +40,7 @@ def list_service_tags(
4140
)
4241
def add_service_tag(
4342
_path_params: Annotated[ServiceTagPathParams, Depends()],
44-
):
45-
...
43+
): ...
4644

4745

4846
@router.post(
@@ -51,5 +49,4 @@ def add_service_tag(
5149
)
5250
def remove_service_tag(
5351
_path_params: Annotated[ServiceTagPathParams, Depends()],
54-
):
55-
...
52+
): ...

services/web/server/src/simcore_service_webserver/catalog/client.py renamed to services/web/server/src/simcore_service_webserver/catalog/_catalog_rest_client_service.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
""" Requests to catalog service API
1+
"""Requests to catalog service API"""
22

3-
"""
4-
import asyncio
53
import logging
64
import urllib.parse
75
from collections.abc import Iterator
@@ -47,7 +45,7 @@ def _handle_client_exceptions(app: web.Application) -> Iterator[ClientSession]:
4745
reason=MSG_CATALOG_SERVICE_UNAVAILABLE
4846
) from err
4947

50-
except (asyncio.TimeoutError, ClientConnectionError) as err:
48+
except (TimeoutError, ClientConnectionError) as err:
5149
_logger.debug("Request to catalog service failed: %s", err)
5250
raise web.HTTPServiceUnavailable(
5351
reason=MSG_CATALOG_SERVICE_UNAVAILABLE

services/web/server/src/simcore_service_webserver/catalog/exceptions.py renamed to services/web/server/src/simcore_service_webserver/catalog/_exceptions.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
"""Defines the different exceptions that may arise in the catalog subpackage"""
22

3+
from servicelib.aiohttp import status
34
from servicelib.rabbitmq.rpc_interfaces.catalog.errors import (
45
CatalogForbiddenError,
56
CatalogItemNotFoundError,
67
)
78

89
from ..errors import WebServerBaseError
10+
from ..exception_handling import (
11+
ExceptionToHttpErrorMap,
12+
HttpErrorInfo,
13+
exception_handling_decorator,
14+
to_exceptions_handlers_map,
15+
)
16+
from ..resource_usage.errors import DefaultPricingPlanNotFoundError
917

1018

1119
class BaseCatalogError(WebServerBaseError):
@@ -35,6 +43,28 @@ def __init__(self, *, service_key: str, service_version: str, **ctxs):
3543
assert CatalogItemNotFoundError # nosec
3644

3745

46+
_TO_HTTP_ERROR_MAP: ExceptionToHttpErrorMap = {
47+
CatalogItemNotFoundError: HttpErrorInfo(
48+
status.HTTP_404_NOT_FOUND,
49+
"Catalog item not found",
50+
),
51+
DefaultPricingPlanNotFoundError: HttpErrorInfo(
52+
status.HTTP_404_NOT_FOUND,
53+
"Default pricing plan not found",
54+
),
55+
DefaultPricingUnitForServiceNotFoundError: HttpErrorInfo(
56+
status.HTTP_404_NOT_FOUND, "Default pricing unit not found"
57+
),
58+
CatalogForbiddenError: HttpErrorInfo(
59+
status.HTTP_403_FORBIDDEN, "Forbidden catalog access"
60+
),
61+
}
62+
63+
handle_plugin_requests_exceptions = exception_handling_decorator(
64+
to_exceptions_handlers_map(_TO_HTTP_ERROR_MAP)
65+
)
66+
67+
3868
__all__: tuple[str, ...] = (
3969
"CatalogForbiddenError",
4070
"CatalogItemNotFoundError",

services/web/server/src/simcore_service_webserver/catalog/_handlers_errors.py

Lines changed: 0 additions & 31 deletions
This file was deleted.

services/web/server/src/simcore_service_webserver/catalog/_handlers.py renamed to services/web/server/src/simcore_service_webserver/catalog/_rest_controller.py

Lines changed: 32 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
""" rest api handlers
1+
"""rest api handlers
22
33
- Take into account that part of the API is also needed in the public API so logic
44
should live in the catalog service in his final version
@@ -7,7 +7,6 @@
77

88
import asyncio
99
import logging
10-
import urllib.parse
1110
from typing import Final
1211

1312
from aiohttp import web
@@ -26,7 +25,7 @@
2625
ServiceResourcesDict,
2726
ServiceResourcesDictHelpers,
2827
)
29-
from pydantic import BaseModel, ConfigDict, Field, field_validator
28+
from pydantic import BaseModel, Field
3029
from servicelib.aiohttp.requests_validation import (
3130
parse_request_body_as,
3231
parse_request_path_parameters_as,
@@ -39,9 +38,16 @@
3938
from ..resource_usage.service import get_default_service_pricing_plan
4039
from ..security.decorators import permission_required
4140
from ..utils_aiohttp import envelope_json_response
42-
from . import _api, _handlers_errors, client
43-
from ._api import CatalogRequestContext
44-
from .exceptions import DefaultPricingUnitForServiceNotFoundError
41+
from . import _catalog_rest_client_service, _service
42+
from ._exceptions import (
43+
DefaultPricingUnitForServiceNotFoundError,
44+
handle_plugin_requests_exceptions,
45+
)
46+
from .controller_rest_schemas import (
47+
CatalogRequestContext,
48+
ListServiceParams,
49+
ServicePathParams,
50+
)
4551

4652
_logger = logging.getLogger(__name__)
4753

@@ -51,41 +57,20 @@
5157
routes = RouteTableDef()
5258

5359

54-
class ServicePathParams(BaseModel):
55-
service_key: ServiceKey
56-
service_version: ServiceVersion
57-
model_config = ConfigDict(
58-
populate_by_name=True,
59-
extra="forbid",
60-
)
61-
62-
@field_validator("service_key", mode="before")
63-
@classmethod
64-
def ensure_unquoted(cls, v):
65-
# NOTE: this is needed as in pytest mode, the aiohttp server does not seem to unquote automatically
66-
if v is not None:
67-
return urllib.parse.unquote(v)
68-
return v
69-
70-
71-
class ListServiceParams(PageQueryParameters):
72-
...
73-
74-
7560
@routes.get(
7661
f"{VTAG}/catalog/services/-/latest",
7762
name="list_services_latest",
7863
)
7964
@login_required
8065
@permission_required("services.catalog.*")
81-
@_handlers_errors.reraise_catalog_exceptions_as_http_errors
66+
@handle_plugin_requests_exceptions
8267
async def list_services_latest(request: Request):
8368
request_ctx = CatalogRequestContext.create(request)
8469
query_params: ListServiceParams = parse_request_query_parameters_as(
8570
ListServiceParams, request
8671
)
8772

88-
page_items, page_meta = await _api.list_latest_services(
73+
page_items, page_meta = await _service.list_latest_services(
8974
request.app,
9075
user_id=request_ctx.user_id,
9176
product_name=request_ctx.product_name,
@@ -116,15 +101,15 @@ async def list_services_latest(request: Request):
116101
)
117102
@login_required
118103
@permission_required("services.catalog.*")
119-
@_handlers_errors.reraise_catalog_exceptions_as_http_errors
104+
@handle_plugin_requests_exceptions
120105
async def get_service(request: Request):
121106
request_ctx = CatalogRequestContext.create(request)
122107
path_params = parse_request_path_parameters_as(ServicePathParams, request)
123108

124109
assert request_ctx # nosec
125110
assert path_params # nosec
126111

127-
service = await _api.get_service_v2(
112+
service = await _service.get_service_v2(
128113
request.app,
129114
user_id=request_ctx.user_id,
130115
product_name=request_ctx.product_name,
@@ -142,7 +127,7 @@ async def get_service(request: Request):
142127
)
143128
@login_required
144129
@permission_required("services.catalog.*")
145-
@_handlers_errors.reraise_catalog_exceptions_as_http_errors
130+
@handle_plugin_requests_exceptions
146131
async def update_service(request: Request):
147132
request_ctx = CatalogRequestContext.create(request)
148133
path_params = parse_request_path_parameters_as(ServicePathParams, request)
@@ -154,7 +139,7 @@ async def update_service(request: Request):
154139
assert path_params # nosec
155140
assert update # nosec
156141

157-
updated = await _api.update_service_v2(
142+
updated = await _service.update_service_v2(
158143
request.app,
159144
user_id=request_ctx.user_id,
160145
product_name=request_ctx.product_name,
@@ -178,7 +163,7 @@ async def list_service_inputs(request: Request):
178163
path_params = parse_request_path_parameters_as(ServicePathParams, request)
179164

180165
# Evaluate and return validated model
181-
response_model = await _api.list_service_inputs(
166+
response_model = await _service.list_service_inputs(
182167
path_params.service_key, path_params.service_version, ctx
183168
)
184169

@@ -203,7 +188,7 @@ async def get_service_input(request: Request):
203188
path_params = parse_request_path_parameters_as(_ServiceInputsPathParams, request)
204189

205190
# Evaluate and return validated model
206-
response_model = await _api.get_service_input(
191+
response_model = await _service.get_service_input(
207192
path_params.service_key,
208193
path_params.service_version,
209194
path_params.input_key,
@@ -236,7 +221,7 @@ async def get_compatible_inputs_given_source_output(request: Request):
236221
)
237222

238223
# Evaluate and return validated model
239-
data = await _api.get_compatible_inputs_given_source_output(
224+
data = await _service.get_compatible_inputs_given_source_output(
240225
path_params.service_key,
241226
path_params.service_version,
242227
query_params.from_service_key,
@@ -261,7 +246,7 @@ async def list_service_outputs(request: Request):
261246
path_params = parse_request_path_parameters_as(ServicePathParams, request)
262247

263248
# Evaluate and return validated model
264-
response_model = await _api.list_service_outputs(
249+
response_model = await _service.list_service_outputs(
265250
path_params.service_key, path_params.service_version, ctx
266251
)
267252

@@ -286,7 +271,7 @@ async def get_service_output(request: Request):
286271
path_params = parse_request_path_parameters_as(_ServiceOutputsPathParams, request)
287272

288273
# Evaluate and return validated model
289-
response_model = await _api.get_service_output(
274+
response_model = await _service.get_service_output(
290275
path_params.service_key,
291276
path_params.service_version,
292277
path_params.output_key,
@@ -323,7 +308,7 @@ async def get_compatible_outputs_given_target_input(request: Request):
323308
_ToServiceInputsParams, request
324309
)
325310

326-
data = await _api.get_compatible_outputs_given_target_input(
311+
data = await _service.get_compatible_outputs_given_target_input(
327312
path_params.service_key,
328313
path_params.service_version,
329314
query_params.to_service_key,
@@ -351,11 +336,13 @@ async def get_service_resources(request: Request):
351336
"""
352337
ctx = CatalogRequestContext.create(request)
353338
path_params = parse_request_path_parameters_as(ServicePathParams, request)
354-
service_resources: ServiceResourcesDict = await client.get_service_resources(
355-
request.app,
356-
user_id=ctx.user_id,
357-
service_key=path_params.service_key,
358-
service_version=path_params.service_version,
339+
service_resources: ServiceResourcesDict = (
340+
await _catalog_rest_client_service.get_service_resources(
341+
request.app,
342+
user_id=ctx.user_id,
343+
service_key=path_params.service_key,
344+
service_version=path_params.service_version,
345+
)
359346
)
360347

361348
data = ServiceResourcesDictHelpers.create_jsonable(service_resources)
@@ -370,7 +357,7 @@ async def get_service_resources(request: Request):
370357
)
371358
@login_required
372359
@permission_required("services.catalog.*")
373-
@_handlers_errors.reraise_catalog_exceptions_as_http_errors
360+
@handle_plugin_requests_exceptions
374361
async def get_service_pricing_plan(request: Request):
375362
ctx = CatalogRequestContext.create(request)
376363
path_params = parse_request_path_parameters_as(ServicePathParams, request)

services/web/server/src/simcore_service_webserver/catalog/_tags_handlers.py renamed to services/web/server/src/simcore_service_webserver/catalog/_rest_tags_controller.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
11
import logging
22

33
from aiohttp import web
4-
from models_library.basic_types import IdInt
54
from servicelib.aiohttp.requests_validation import parse_request_path_parameters_as
65

76
from .._meta import API_VTAG
87
from ..login.decorators import login_required
98
from ..security.decorators import permission_required
10-
from ._handlers import ServicePathParams
9+
from .controller_rest_schemas import ServicePathParams, ServiceTagPathParams
1110

1211
_logger = logging.getLogger(__name__)
1312

1413

15-
class ServiceTagPathParams(ServicePathParams):
16-
tag_id: IdInt
17-
18-
1914
routes = web.RouteTableDef()
2015

2116

0 commit comments

Comments
 (0)