Skip to content

✨ web-api: Adds endpoint to retrieve project services with Release and Access Information #7287

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
155bb91
draft project/services api
pcrespov Feb 26, 2025
bcf3fa4
draft test and api in catalog
pcrespov Feb 26, 2025
1ff3970
updates OAS
pcrespov Feb 26, 2025
36d0150
draft
pcrespov Feb 26, 2025
c110249
draft 2
pcrespov Feb 26, 2025
1f9da9e
draft 3
pcrespov Feb 27, 2025
504a11d
fix tests
pcrespov Feb 27, 2025
19cc9d1
expanding test
pcrespov Feb 27, 2025
50f2de1
update
pcrespov Feb 27, 2025
d2f8ec9
connect to rpc
pcrespov Feb 27, 2025
f5a7c40
updates listing
pcrespov Feb 27, 2025
43ae5b0
fix tests
pcrespov Feb 27, 2025
9479efd
cleanup
pcrespov Feb 27, 2025
dfd365e
drop api changes
pcrespov Feb 27, 2025
13013e3
items
pcrespov Feb 27, 2025
47d96c6
other user fixture
pcrespov Feb 27, 2025
3451d57
fix tests
pcrespov Feb 28, 2025
e2bfac8
drafts test
pcrespov Feb 28, 2025
18c0dd5
fixes models
pcrespov Feb 28, 2025
b3aed7d
minor oas changes
pcrespov Feb 28, 2025
52319ab
adds get_project_services handle
pcrespov Feb 28, 2025
99e999c
drafts test
pcrespov Feb 28, 2025
ead3316
implementing get_project_nodes_services
pcrespov Feb 28, 2025
a79eb6d
adds test
pcrespov Feb 28, 2025
4c5c868
fixes mypy
pcrespov Feb 28, 2025
e663629
fixes pylint
pcrespov Feb 28, 2025
a6f3051
fixes tests
pcrespov Feb 28, 2025
02bb0fa
updates OAS
pcrespov Feb 28, 2025
323ab38
services/webserver api version: 0.60.0 → 0.61.0
pcrespov Feb 28, 2025
d1ae3d9
adds access check
pcrespov Feb 28, 2025
b31ab1f
accepts no ownership
pcrespov Feb 28, 2025
f5c9a28
updates OAS
pcrespov Feb 28, 2025
31d0f11
cleanup
pcrespov Feb 28, 2025
ea7a012
extends test
pcrespov Feb 28, 2025
8183622
fixes pylint
pcrespov Feb 28, 2025
924c083
Merge branch 'master' into is6201/catalog-history-alternative
pcrespov Mar 3, 2025
056bedc
fix pylint
pcrespov Mar 3, 2025
0510b0b
fix sonar
pcrespov Mar 3, 2025
c53cab9
@GitHK review: validate on server side
pcrespov Mar 3, 2025
5131518
Merge branch 'master' into is6201/catalog-history-alternative
pcrespov Mar 3, 2025
6797b92
Merge branch 'master' into is6201/catalog-history-alternative
pcrespov Mar 3, 2025
b4035d5
@sanderegg review: rename error
pcrespov Mar 4, 2025
6b02ba2
Merge branch 'master' into is6201/catalog-history-alternative
pcrespov Mar 4, 2025
b7e4327
Merge branch 'master' into is6201/catalog-history-alternative
pcrespov Mar 4, 2025
517952d
Merge branch 'master' into is6201/catalog-history-alternative
pcrespov Mar 4, 2025
a7f864a
Merge branch 'master' into is6201/catalog-history-alternative
pcrespov Mar 5, 2025
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
64 changes: 25 additions & 39 deletions api/specs/web-server/_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from models_library.api_schemas_api_server.pricing_plans import ServicePricingPlanGet
from models_library.api_schemas_webserver.catalog import (
CatalogServiceGet,
CatalogServiceListItem,
CatalogServiceUpdate,
ServiceInputGet,
ServiceInputKey,
Expand Down Expand Up @@ -31,108 +32,94 @@
)


#
# /catalog/services/* COLLECTION
#


@router.get(
"/catalog/services/-/latest",
response_model=Page[CatalogServiceGet],
response_model=Page[CatalogServiceListItem],
)
def list_services_latest(_query_params: Annotated[ListServiceParams, Depends()]):
def list_services_latest(_query: Annotated[ListServiceParams, Depends()]):
pass


@router.get(
"/catalog/services/{service_key}/{service_version}",
response_model=Envelope[CatalogServiceGet],
)
def get_service(_path_params: Annotated[ServicePathParams, Depends()]):
...
def get_service(_path: Annotated[ServicePathParams, Depends()]): ...


@router.patch(
"/catalog/services/{service_key}/{service_version}",
response_model=Envelope[CatalogServiceGet],
)
def update_service(
_path_params: Annotated[ServicePathParams, Depends()],
_update: CatalogServiceUpdate,
):
...
_path: Annotated[ServicePathParams, Depends()],
_body: CatalogServiceUpdate,
): ...


@router.get(
"/catalog/services/{service_key}/{service_version}/inputs",
response_model=Envelope[list[ServiceInputGet]],
)
def list_service_inputs(
_path_params: Annotated[ServicePathParams, Depends()],
):
...
_path: Annotated[ServicePathParams, Depends()],
): ...


@router.get(
"/catalog/services/{service_key}/{service_version}/inputs/{input_key}",
response_model=Envelope[ServiceInputGet],
)
def get_service_input(
_path_params: Annotated[_ServiceInputsPathParams, Depends()],
):
...
_path: Annotated[_ServiceInputsPathParams, Depends()],
): ...


@router.get(
"/catalog/services/{service_key}/{service_version}/inputs:match",
response_model=Envelope[list[ServiceInputKey]],
)
def get_compatible_inputs_given_source_output(
_path_params: Annotated[ServicePathParams, Depends()],
_query_params: Annotated[_FromServiceOutputParams, Depends()],
):
...
_path: Annotated[ServicePathParams, Depends()],
_query: Annotated[_FromServiceOutputParams, Depends()],
): ...


@router.get(
"/catalog/services/{service_key}/{service_version}/outputs",
response_model=Envelope[list[ServiceOutputKey]],
)
def list_service_outputs(
_path_params: Annotated[ServicePathParams, Depends()],
):
...
_path: Annotated[ServicePathParams, Depends()],
): ...


@router.get(
"/catalog/services/{service_key}/{service_version}/outputs/{output_key}",
response_model=Envelope[list[ServiceOutputGet]],
)
def get_service_output(
_path_params: Annotated[_ServiceOutputsPathParams, Depends()],
):
...
_path: Annotated[_ServiceOutputsPathParams, Depends()],
): ...


@router.get(
"/catalog/services/{service_key}/{service_version}/outputs:match",
response_model=Envelope[list[ServiceOutputKey]],
)
def get_compatible_outputs_given_target_input(
_path_params: Annotated[ServicePathParams, Depends()],
_query_params: Annotated[_ToServiceInputsParams, Depends()],
):
...
_path: Annotated[ServicePathParams, Depends()],
_query: Annotated[_ToServiceInputsParams, Depends()],
): ...


@router.get(
"/catalog/services/{service_key}/{service_version}/resources",
response_model=Envelope[ServiceResourcesGet],
)
def get_service_resources(
_params: Annotated[ServicePathParams, Depends()],
):
...
_path: Annotated[ServicePathParams, Depends()],
): ...


@router.get(
Expand All @@ -142,6 +129,5 @@ def get_service_resources(
tags=["pricing-plans"],
)
async def get_service_pricing_plan(
_params: Annotated[ServicePathParams, Depends()],
):
...
_path: Annotated[ServicePathParams, Depends()],
): ...
20 changes: 12 additions & 8 deletions api/specs/web-server/_projects_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
NodePatch,
NodeRetrieve,
NodeRetrieved,
ProjectNodeServicesGet,
ServiceResourcesDict,
)
from models_library.generics import Envelope
Expand Down Expand Up @@ -76,8 +77,7 @@ def delete_node(project_id: str, node_id: str): # noqa: ARG001
)
def retrieve_node(
project_id: str, node_id: str, _retrieve: NodeRetrieve # noqa: ARG001
):
...
): ...


@router.post(
Expand Down Expand Up @@ -147,24 +147,29 @@ def get_node_resources(project_id: str, node_id: str): # noqa: ARG001
)
def replace_node_resources(
project_id: str, node_id: str, _new: ServiceResourcesDict # noqa: ARG001
):
...
): ...


#
# projects/*/nodes/-/services
#


@router.get(
"/projects/{project_id}/nodes/-/services",
response_model=Envelope[ProjectNodeServicesGet],
)
async def get_project_services(project_id: ProjectID): ...


@router.get(
"/projects/{project_id}/nodes/-/services:access",
response_model=Envelope[_ProjectGroupAccess],
description="Check whether provided group has access to the project services",
)
async def get_project_services_access_for_gid(
project_id: ProjectID, for_gid: GroupID # noqa: ARG001
):
...
): ...


assert_handler_signature_against_model(
Expand Down Expand Up @@ -197,8 +202,7 @@ async def list_project_nodes_previews(project_id: ProjectID): # noqa: ARG001
)
async def get_project_node_preview(
project_id: ProjectID, node_id: NodeID # noqa: ARG001
):
...
): ...


assert_handler_signature_against_model(get_project_node_preview, NodePathParams)
15 changes: 12 additions & 3 deletions packages/models-library/src/models_library/access_rights.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
from typing import Annotated

from pydantic import BaseModel, ConfigDict, Field


class AccessRights(BaseModel):
read: bool = Field(..., description="has read access")
write: bool = Field(..., description="has write access")
delete: bool = Field(..., description="has deletion rights")
read: Annotated[bool, Field(description="has read access")]
write: Annotated[bool, Field(description="has write access")]
delete: Annotated[bool, Field(description="has deletion rights")]

model_config = ConfigDict(extra="forbid")


class ExecutableAccessRights(BaseModel):
write: Annotated[bool, Field(description="can change executable settings")]
execute: Annotated[bool, Field(description="can run executable")]

model_config = ConfigDict(extra="forbid")
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from datetime import datetime
from typing import Any, TypeAlias
from typing import Annotated, Any, TypeAlias

from common_library.basic_types import DEFAULT_FACTORY
from models_library.rpc_pagination import PageRpc
from pydantic import ConfigDict, Field, HttpUrl, NonNegativeInt
from pydantic.config import JsonDict
Expand Down Expand Up @@ -154,9 +155,10 @@
class ServiceGet(
ServiceMetaDataPublished, ServiceAccessRights, ServiceMetaDataEditable
): # pylint: disable=too-many-ancestors
owner: LowerCaseEmailStr | None = Field(
description="None when the owner email cannot be found in the database"
)
owner: Annotated[
LowerCaseEmailStr | None,
Field(description="None when the owner email cannot be found in the database"),
]

@staticmethod
def _update_json_schema_extra(schema: JsonDict) -> None:
Expand All @@ -169,7 +171,7 @@ def _update_json_schema_extra(schema: JsonDict) -> None:
)


class ServiceGetV2(CatalogOutputSchema):
class _BaseServiceGetV2(CatalogOutputSchema):
# Model used in catalog's rpc and rest interfaces
key: ServiceKey
version: ServiceVersion
Expand All @@ -183,13 +185,14 @@ class ServiceGetV2(CatalogOutputSchema):

version_display: str | None = None

service_type: ServiceType = Field(default=..., alias="type")
service_type: Annotated[ServiceType, Field(alias="type")]

contact: LowerCaseEmailStr | None
authors: list[Author] = Field(..., min_length=1)
owner: LowerCaseEmailStr | None = Field(
description="None when the owner email cannot be found in the database"
)
authors: Annotated[list[Author], Field(min_length=1)]
owner: Annotated[
LowerCaseEmailStr | None,
Field(description="None when the owner email cannot be found in the database"),
]

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

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


class ServiceGetV2(_BaseServiceGetV2):
# Model used in catalog's rpc and rest interfaces
history: Annotated[
list[ServiceRelease],
Field(
default_factory=list,
description="history of releases for this service at this point in time, starting from the newest to the oldest."
" It includes current release.",
json_schema_extra={"default": []},
),
] = DEFAULT_FACTORY

@staticmethod
def _update_json_schema_extra(schema: JsonDict) -> None:
schema.update(
Expand Down Expand Up @@ -269,16 +284,25 @@ def _update_json_schema_extra(schema: JsonDict) -> None:
)

model_config = ConfigDict(
extra="forbid",
populate_by_name=True,
alias_generator=snake_to_camel,
json_schema_extra=_update_json_schema_extra,
)


class ServiceListItem(_BaseServiceGetV2):
history: Annotated[
list[ServiceRelease],
Field(
default_factory=list,
deprecated=True,
description="History will be replaced by current 'release' instead",
json_schema_extra={"default": []},
),
] = DEFAULT_FACTORY


PageRpcServicesGetV2: TypeAlias = PageRpc[
# WARNING: keep this definition in models_library and not in the RPC interface
ServiceGetV2
ServiceListItem
]

ServiceResourcesGet: TypeAlias = ServiceResourcesDict
Expand Down Expand Up @@ -310,3 +334,11 @@ class ServiceUpdateV2(CatalogInputSchema):
assert set(ServiceUpdateV2.model_fields.keys()) - set( # nosec
ServiceGetV2.model_fields.keys()
) == {"deprecated"}


class MyServiceGet(CatalogOutputSchema):
key: ServiceKey
release: ServiceRelease

owner: GroupID | None
my_access_rights: ServiceGroupAccessRightsV2
Loading
Loading