Skip to content
Merged
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
4 changes: 3 additions & 1 deletion api/specs/web-server/_auth_api_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
from models_library.rest_error import EnvelopedError
from simcore_service_webserver._meta import API_VTAG
from simcore_service_webserver.api_keys._controller_rest import ApiKeysPathParams
from simcore_service_webserver.api_keys._exceptions_handlers import _TO_HTTP_ERROR_MAP
from simcore_service_webserver.api_keys._controller_rest_exceptions import (
_TO_HTTP_ERROR_MAP,
)

router = APIRouter(
prefix=f"/{API_VTAG}",
Expand Down
18 changes: 9 additions & 9 deletions api/specs/web-server/_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
from models_library.generics import Envelope
from models_library.rest_pagination import Page
from simcore_service_webserver._meta import API_VTAG
from simcore_service_webserver.catalog._rest_controller import (
from simcore_service_webserver.catalog._controller_rest_schemas import (
FromServiceOutputQueryParams,
ListServiceParams,
ServiceInputsPathParams,
ServiceOutputsPathParams,
ServicePathParams,
_FromServiceOutputParams,
_ServiceInputsPathParams,
_ServiceOutputsPathParams,
_ToServiceInputsParams,
ToServiceInputsQueryParams,
)

router = APIRouter(
Expand Down Expand Up @@ -71,7 +71,7 @@ def list_service_inputs(
response_model=Envelope[ServiceInputGet],
)
def get_service_input(
_path: Annotated[_ServiceInputsPathParams, Depends()],
_path: Annotated[ServiceInputsPathParams, Depends()],
): ...


Expand All @@ -81,7 +81,7 @@ def get_service_input(
)
def get_compatible_inputs_given_source_output(
_path: Annotated[ServicePathParams, Depends()],
_query: Annotated[_FromServiceOutputParams, Depends()],
_query: Annotated[FromServiceOutputQueryParams, Depends()],
): ...


Expand All @@ -99,7 +99,7 @@ def list_service_outputs(
response_model=Envelope[list[ServiceOutputGet]],
)
def get_service_output(
_path: Annotated[_ServiceOutputsPathParams, Depends()],
_path: Annotated[ServiceOutputsPathParams, Depends()],
): ...


Expand All @@ -109,7 +109,7 @@ def get_service_output(
)
def get_compatible_outputs_given_target_input(
_path: Annotated[ServicePathParams, Depends()],
_query: Annotated[_ToServiceInputsParams, Depends()],
_query: Annotated[ToServiceInputsQueryParams, Depends()],
): ...


Expand Down
2 changes: 1 addition & 1 deletion api/specs/web-server/_catalog_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from models_library.api_schemas_webserver.catalog import CatalogServiceGet
from models_library.generics import Envelope
from simcore_service_webserver._meta import API_VTAG
from simcore_service_webserver.catalog._rest_tags_controller import (
from simcore_service_webserver.catalog._controller_rest_schemas import (
ServicePathParams,
ServiceTagPathParams,
)
Expand Down
25 changes: 15 additions & 10 deletions packages/models-library/src/models_library/rest_pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
int, Field(ge=1, lt=MAXIMUM_NUMBER_OF_ITEMS_PER_PAGE)
]

PageOffsetInt: TypeAlias = NonNegativeInt

DEFAULT_NUMBER_OF_ITEMS_PER_PAGE: Final[PageLimitInt] = TypeAdapter(
PageLimitInt
).validate_python(20)
Expand All @@ -51,15 +53,19 @@ class CursorQueryParameters(RequestParameters):
class PageQueryParameters(RequestParameters):
"""Use as pagination options in query parameters"""

limit: PageLimitInt = Field(
default=TypeAdapter(PageLimitInt).validate_python(
DEFAULT_NUMBER_OF_ITEMS_PER_PAGE
limit: Annotated[
PageLimitInt,
Field(
default=TypeAdapter(PageLimitInt).validate_python(
DEFAULT_NUMBER_OF_ITEMS_PER_PAGE
),
description="maximum number of items to return (pagination)",
),
description="maximum number of items to return (pagination)",
)
offset: NonNegativeInt = Field(
default=0, description="index to the first item to return (pagination)"
)
]
offset: Annotated[
PageOffsetInt,
Field(default=0, description="index to the first item to return (pagination)"),
]


class PageMetaInfoLimitOffset(BaseModel):
Expand Down Expand Up @@ -120,8 +126,7 @@ class PageLinks(
BeforeValidator(lambda x: str(TypeAdapter(AnyHttpUrl).validate_python(x))),
]
]
):
...
): ...


ItemT = TypeVar("ItemT")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from ..security.decorators import permission_required
from ..utils_aiohttp import envelope_json_response
from . import _service
from ._exceptions_handlers import handle_plugin_requests_exceptions
from ._controller_rest_exceptions import handle_plugin_requests_exceptions
from .models import ApiKey

_logger = logging.getLogger(__name__)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,11 @@
from servicelib.aiohttp import status
from servicelib.aiohttp.client_session import get_client_session
from servicelib.rest_constants import X_PRODUCT_NAME_HEADER
from settings_library.catalog import CatalogSettings
from yarl import URL

from .._meta import api_version_prefix
from ._constants import MSG_CATALOG_SERVICE_NOT_FOUND, MSG_CATALOG_SERVICE_UNAVAILABLE
from .settings import get_plugin_settings
from .settings import CatalogSettings, get_plugin_settings

_logger = logging.getLogger(__name__)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,14 @@
from models_library.api_schemas_webserver.catalog import (
CatalogServiceGet,
CatalogServiceUpdate,
ServiceInputKey,
ServiceOutputKey,
)
from models_library.api_schemas_webserver.resource_usage import PricingPlanGet
from models_library.rest_pagination import Page, PageQueryParameters
from models_library.rest_pagination import Page
from models_library.rest_pagination_utils import paginate_data
from models_library.services import ServiceKey, ServiceVersion
from models_library.services_resources import (
ServiceResourcesDict,
ServiceResourcesDictHelpers,
)
from pydantic import BaseModel, Field
from servicelib.aiohttp.requests_validation import (
parse_request_body_as,
parse_request_path_parameters_as,
Expand All @@ -39,14 +35,19 @@
from ..security.decorators import permission_required
from ..utils_aiohttp import envelope_json_response
from . import _catalog_rest_client_service, _service
from ._exceptions import (
from ._controller_rest_exceptions import (
DefaultPricingUnitForServiceNotFoundError,
handle_plugin_requests_exceptions,
)
from .controller_rest_schemas import (
from ._controller_rest_schemas import (
CatalogRequestContext,
FromServiceOutputQueryParams,
ListServiceParams,
ServiceInputsPathParams,
ServiceOutputsPathParams,
ServicePathParams,
ServiceTagPathParams,
ToServiceInputsQueryParams,
)

_logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -75,9 +76,8 @@ async def list_services_latest(request: Request):
user_id=request_ctx.user_id,
product_name=request_ctx.product_name,
unit_registry=request_ctx.unit_registry,
page_params=PageQueryParameters.model_construct(
offset=query_params.offset, limit=query_params.limit
),
offset=query_params.offset,
limit=query_params.limit,
)

assert page_meta.limit == query_params.limit # nosec
Expand Down Expand Up @@ -173,10 +173,6 @@ async def list_service_inputs(request: Request):
)


class _ServiceInputsPathParams(ServicePathParams):
input_key: ServiceInputKey


@routes.get(
f"{VTAG}/catalog/services/{{service_key}}/{{service_version}}/inputs/{{input_key}}",
name="get_service_input",
Expand All @@ -185,7 +181,7 @@ class _ServiceInputsPathParams(ServicePathParams):
@permission_required("services.catalog.*")
async def get_service_input(request: Request):
ctx = CatalogRequestContext.create(request)
path_params = parse_request_path_parameters_as(_ServiceInputsPathParams, request)
path_params = parse_request_path_parameters_as(ServiceInputsPathParams, request)

# Evaluate and return validated model
response_model = await _service.get_service_input(
Expand All @@ -201,12 +197,6 @@ async def get_service_input(request: Request):
)


class _FromServiceOutputParams(BaseModel):
from_service_key: ServiceKey = Field(..., alias="fromService")
from_service_version: ServiceVersion = Field(..., alias="fromVersion")
from_output_key: ServiceOutputKey = Field(..., alias="fromOutput")


@routes.get(
f"{VTAG}/catalog/services/{{service_key}}/{{service_version}}/inputs:match",
name="get_compatible_inputs_given_source_output",
Expand All @@ -216,8 +206,8 @@ class _FromServiceOutputParams(BaseModel):
async def get_compatible_inputs_given_source_output(request: Request):
ctx = CatalogRequestContext.create(request)
path_params = parse_request_path_parameters_as(ServicePathParams, request)
query_params: _FromServiceOutputParams = parse_request_query_parameters_as(
_FromServiceOutputParams, request
query_params: FromServiceOutputQueryParams = parse_request_query_parameters_as(
FromServiceOutputQueryParams, request
)

# Evaluate and return validated model
Expand Down Expand Up @@ -256,10 +246,6 @@ async def list_service_outputs(request: Request):
)


class _ServiceOutputsPathParams(ServicePathParams):
output_key: ServiceOutputKey


@routes.get(
f"{VTAG}/catalog/services/{{service_key}}/{{service_version}}/outputs/{{output_key}}",
name="get_service_output",
Expand All @@ -268,7 +254,7 @@ class _ServiceOutputsPathParams(ServicePathParams):
@permission_required("services.catalog.*")
async def get_service_output(request: Request):
ctx = CatalogRequestContext.create(request)
path_params = parse_request_path_parameters_as(_ServiceOutputsPathParams, request)
path_params = parse_request_path_parameters_as(ServiceOutputsPathParams, request)

# Evaluate and return validated model
response_model = await _service.get_service_output(
Expand All @@ -284,12 +270,6 @@ async def get_service_output(request: Request):
)


class _ToServiceInputsParams(BaseModel):
to_service_key: ServiceKey = Field(..., alias="toService")
to_service_version: ServiceVersion = Field(..., alias="toVersion")
to_input_key: ServiceInputKey = Field(..., alias="toInput")


@routes.get(
f"{VTAG}/catalog/services/{{service_key}}/{{service_version}}/outputs:match",
name="get_compatible_outputs_given_target_input",
Expand All @@ -304,8 +284,8 @@ async def get_compatible_outputs_given_target_input(request: Request):
"""
ctx = CatalogRequestContext.create(request)
path_params = parse_request_path_parameters_as(ServicePathParams, request)
query_params: _ToServiceInputsParams = parse_request_query_parameters_as(
_ToServiceInputsParams, request
query_params: ToServiceInputsQueryParams = parse_request_query_parameters_as(
ToServiceInputsQueryParams, request
)

data = await _service.get_compatible_outputs_given_target_input(
Expand Down Expand Up @@ -377,3 +357,43 @@ async def get_service_pricing_plan(request: Request):
return envelope_json_response(
PricingPlanGet.model_validate(pricing_plan.model_dump())
)


@routes.get(
f"/{API_VTAG}/catalog/services/{{service_key}}/{{service_version}}/tags",
name="list_service_tags",
)
@login_required
@permission_required("service.tag.*")
async def list_service_tags(request: web.Request):
path_params = parse_request_path_parameters_as(ServicePathParams, request)
assert path_params # nosec
raise NotImplementedError


@routes.post(
f"/{API_VTAG}/catalog/services/{{service_key}}/{{service_version}}/tags/{{tag_id}}:add",
name="add_service_tag",
)
@login_required
@permission_required("service.tag.*")
async def add_service_tag(request: web.Request):
path_params = parse_request_path_parameters_as(ServiceTagPathParams, request)
assert path_params # nosec

# responds with parent's resource to get the current state (as with patch/update)
raise NotImplementedError


@routes.post(
f"/{API_VTAG}/catalog/services/{{service_key}}/{{service_version}}/tags/{{tag_id}}:remove",
name="remove_service_tag",
)
@login_required
@permission_required("service.tag.*")
async def remove_service_tag(request: web.Request):
path_params = parse_request_path_parameters_as(ServiceTagPathParams, request)
assert path_params # nosec

# responds with parent's resource to get the current state (as with patch/update)
raise NotImplementedError
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,14 @@
CatalogItemNotFoundError,
)

from ..errors import WebServerBaseError
from ..exception_handling import (
ExceptionToHttpErrorMap,
HttpErrorInfo,
exception_handling_decorator,
to_exceptions_handlers_map,
)
from ..resource_usage.errors import DefaultPricingPlanNotFoundError


class BaseCatalogError(WebServerBaseError):
msg_template = "Unexpected error occured in catalog submodule"

def __init__(self, msg=None, **ctx):
super().__init__(**ctx)
if msg:
self.msg_template = msg

def debug_message(self):
# Override in subclass
return f"{self.code}: {self}"


class DefaultPricingUnitForServiceNotFoundError(BaseCatalogError):
msg_template = "Default pricing unit not found for service key '{service_key}' and version '{service_version}'"

def __init__(self, *, service_key: str, service_version: str, **ctxs):
super().__init__(**ctxs)
self.service_key = service_key
self.service_version = service_version

from .errors import DefaultPricingUnitForServiceNotFoundError

# mypy: disable-error-code=truthy-function
assert CatalogForbiddenError # nosec
Expand Down
Loading
Loading