Skip to content
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from pydantic import BaseModel


class CatalogInputSchema(BaseModel):
...


class CatalogOutputSchema(BaseModel):
...
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import Any, TypeAlias

from models_library.rpc_pagination import PageRpc
from pydantic import BaseModel, ConfigDict, Field, HttpUrl, NonNegativeInt
from pydantic import ConfigDict, Field, HttpUrl, NonNegativeInt
from pydantic.config import JsonDict

from ..boot_options import BootOptions
Expand All @@ -21,6 +21,7 @@
from ..services_resources import ServiceResourcesDict
from ..services_types import ServiceKey, ServiceVersion
from ..utils.change_case import snake_to_camel
from ._base import CatalogInputSchema, CatalogOutputSchema

_EXAMPLE_FILEPICKER: dict[str, Any] = {
"name": "File Picker",
Expand Down Expand Up @@ -71,6 +72,7 @@
"thumbnail": None,
"description": "A service which awaits for time to pass, two times.",
"description_ui": True,
"icon": "https://cdn-icons-png.flaticon.com/512/25/25231.png",
"classifiers": [],
"quality": {},
"accessRights": {"1": {"execute": True, "write": False}},
Expand Down Expand Up @@ -167,12 +169,14 @@ def _update_json_schema_extra(schema: JsonDict) -> None:
)


class ServiceGetV2(BaseModel):
class ServiceGetV2(CatalogOutputSchema):
# Model used in catalog's rpc and rest interfaces
key: ServiceKey
version: ServiceVersion

name: str
thumbnail: HttpUrl | None = None
icon: HttpUrl | None = None
description: str

description_ui: bool = False
Expand Down Expand Up @@ -280,9 +284,10 @@ def _update_json_schema_extra(schema: JsonDict) -> None:
ServiceResourcesGet: TypeAlias = ServiceResourcesDict


class ServiceUpdateV2(BaseModel):
class ServiceUpdateV2(CatalogInputSchema):
name: str | None = None
thumbnail: HttpUrl | None = None
icon: HttpUrl | None = None

description: str | None = None
description_ui: bool = False
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Annotated

from pydantic import BaseModel, ConfigDict, Field, field_validator
from pydantic import BaseModel, ConfigDict, Field, HttpUrl, field_validator

from .services_types import ServiceKey, ServiceVersion
from .utils.common_validators import empty_str_to_none_pre_validator
Expand Down Expand Up @@ -41,6 +41,7 @@ class ServiceBaseDisplay(BaseModel):
validate_default=True,
),
] = None
icon: Annotated[HttpUrl | None, Field(description="URL to the service icon")] = None
description: Annotated[
str,
Field(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import Annotated, Any

from common_library.basic_types import DEFAULT_FACTORY
from pydantic import ConfigDict, Field
from pydantic import ConfigDict, Field, HttpUrl
from pydantic.config import JsonDict

from .services_base import ServiceBaseDisplay
Expand All @@ -22,18 +22,21 @@ class ServiceMetaDataEditable(ServiceBaseDisplay):
# Overrides ServiceBaseDisplay fields to Optional for a partial update
name: str | None # type: ignore[assignment]
thumbnail: str | None
icon: HttpUrl | None
description: str | None # type: ignore[assignment]
description_ui: bool = False
version_display: str | None = None

# Below fields only in the database ----
deprecated: datetime | None = Field(
default=None,
description="Owner can set the date to retire the service. Three possibilities:"
"If None, the service is marked as `published`;"
"If now<deprecated the service is marked as deprecated;"
"If now>=deprecated, the service is retired",
)
deprecated: Annotated[
datetime | None,
Field(
description="Owner can set the date to retire the service. Three possibilities:"
"If None, the service is marked as `published`;"
"If now<deprecated the service is marked as deprecated;"
"If now>=deprecated, the service is retired",
),
] = None
classifiers: list[str] | None
quality: Annotated[
dict[str, Any], Field(default_factory=dict, json_schema_extra={"default": {}})
Expand All @@ -49,6 +52,7 @@ def _update_json_schema_extra(schema: JsonDict) -> None:
"name": "sim4life",
"description": "s4l web",
"thumbnail": "https://thumbnailit.org/image",
"icon": "https://cdn-icons-png.flaticon.com/512/25/25231.png",
"quality": {
"enabled": True,
"tsr_target": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""new icon table

Revision ID: 3fe27ff48f73
Revises: e71ea59858f4
Create Date: 2025-02-05 16:50:02.419293+00:00

"""
import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "3fe27ff48f73"
down_revision = "e71ea59858f4"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column("services_meta_data", sa.Column("icon", sa.String(), nullable=True))
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("services_meta_data", "icon")
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@
nullable=True,
doc="Link to image to us as service thumbnail (editable)",
),
sa.Column(
"icon",
sa.String,
nullable=True,
doc="Link to icon (editable)",
),
sa.Column(
"version_display",
sa.String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@
DEFAULT_FAKER: Final = faker.Faker()


def random_icon_url(fake: Faker):
return fake.image_url(width=16, height=16)


def random_thumbnail_url(fake: Faker):
return fake.image_url(width=32, height=32)


def _compute_hash(password: str) -> str:
try:
# 'passlib' will be used only if already installed.
Expand Down Expand Up @@ -396,7 +404,8 @@ def random_service_meta_data(
# optional
"description_ui": fake.pybool(),
"owner": owner_primary_gid,
"thumbnail": _pick_from([fake.image_url(), None]), # nullable
"thumbnail": _pick_from([random_thumbnail_url(fake), None]), # nullable
"icon": _pick_from([random_icon_url(fake), None]), # nullable
"version_display": _pick_from([f"v{_version}", None]), # nullable
"classifiers": [], # has default
"quality": {}, # has default
Expand Down
2 changes: 1 addition & 1 deletion services/catalog/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.7.0
0.8.0
17 changes: 16 additions & 1 deletion services/catalog/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"info": {
"title": "simcore-service-catalog",
"description": "Manages and maintains a catalog of all published components (e.g. macro-algorithms, scripts, etc)",
"version": "0.7.0"
"version": "0.8.0"
},
"paths": {
"/": {
Expand Down Expand Up @@ -2618,6 +2618,21 @@
"title": "Thumbnail",
"description": "URL to the service thumbnail"
},
"icon": {
"anyOf": [
{
"type": "string",
"maxLength": 2083,
"minLength": 1,
"format": "uri"
},
{
"type": "null"
}
],
"title": "Icon",
"description": "URL to the service icon"
},
"description": {
"type": "string",
"title": "Description",
Expand Down
2 changes: 1 addition & 1 deletion services/catalog/setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.7.0
current_version = 0.8.0
commit = True
message = services/catalog version: {current_version} → {new_version}
tag = False
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
)
from ...db.repositories.groups import GroupsRepository
from ...db.repositories.services import ServicesRepository
from ...models.services_db import ServiceAccessRightsAtDB, ServiceMetaDataAtDB
from ...models.services_db import ServiceAccessRightsAtDB, ServiceMetaDataDBGet
from ...services.director import DirectorApi
from ..dependencies.database import get_repository
from ..dependencies.director import get_director_api
Expand All @@ -35,7 +35,7 @@

def _compose_service_details(
service_in_registry: dict[str, Any], # published part
service_in_db: ServiceMetaDataAtDB, # editable part
service_in_db: ServiceMetaDataDBGet, # editable part
service_access_rights_in_db: list[ServiceAccessRightsAtDB],
service_owner: str | None,
) -> ServiceGet | None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from ..db.repositories.groups import GroupsRepository
from ..db.repositories.projects import ProjectsRepository
from ..db.repositories.services import ServicesRepository
from ..models.services_db import ServiceAccessRightsAtDB, ServiceMetaDataAtDB
from ..models.services_db import ServiceAccessRightsAtDB, ServiceMetaDataDBCreate
from ..services import access_rights

_logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -89,7 +89,9 @@ def _by_version(t: tuple[ServiceKey, ServiceVersion]) -> Version:

# set the service in the DB
await services_repo.create_or_update_service(
ServiceMetaDataAtDB(**service_metadata.model_dump(), owner=owner_gid),
ServiceMetaDataDBCreate(
**service_metadata.model_dump(exclude_unset=True), owner=owner_gid
),
service_access_rights,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
from models_library.products import ProductName
from models_library.services_types import ServiceKey, ServiceVersion
from models_library.users import UserID
from simcore_postgres_database.utils_repos import get_columns_from_db_model
from sqlalchemy.dialects.postgresql import ARRAY, INTEGER, array_agg
from sqlalchemy.sql import and_, or_
from sqlalchemy.sql.expression import func
from sqlalchemy.sql.selectable import Select

from ...models.services_db import ServiceMetaDataDBGet
from ..tables import (
services_access_rights,
services_compatibility,
Expand All @@ -17,6 +19,10 @@
users,
)

SERVICES_META_DATA_COLS = get_columns_from_db_model(
services_meta_data, ServiceMetaDataDBGet
)


def list_services_stmt(
*,
Expand All @@ -26,7 +32,7 @@ def list_services_stmt(
combine_access_with_and: bool | None = True,
product_name: str | None = None,
) -> Select:
stmt = sa.select(services_meta_data)
stmt = sa.select(SERVICES_META_DATA_COLS)
if gids or execute_access or write_access:
conditions: list[Any] = []

Expand All @@ -50,13 +56,9 @@ def list_services_stmt(
if product_name:
conditions.append(services_access_rights.c.product_name == product_name)

stmt = (
sa.select(
services_meta_data,
)
.distinct(services_meta_data.c.key, services_meta_data.c.version)
.select_from(services_meta_data.join(services_access_rights))
)
stmt = stmt.distinct(
services_meta_data.c.key, services_meta_data.c.version
).select_from(services_meta_data.join(services_access_rights))
if conditions:
stmt = stmt.where(and_(*conditions))
stmt = stmt.order_by(services_meta_data.c.key, services_meta_data.c.version)
Expand Down Expand Up @@ -181,6 +183,7 @@ def list_latest_services_with_history_stmt(
services_meta_data.c.description,
services_meta_data.c.description_ui,
services_meta_data.c.thumbnail,
services_meta_data.c.icon,
services_meta_data.c.version_display,
services_meta_data.c.classifiers,
services_meta_data.c.created,
Expand Down Expand Up @@ -273,6 +276,7 @@ def list_latest_services_with_history_stmt(
latest_query.c.description,
latest_query.c.description_ui,
latest_query.c.thumbnail,
latest_query.c.icon,
latest_query.c.version_display,
# ownership
latest_query.c.owner_email,
Expand Down Expand Up @@ -312,6 +316,7 @@ def list_latest_services_with_history_stmt(
latest_query.c.description,
latest_query.c.description_ui,
latest_query.c.thumbnail,
latest_query.c.icon,
latest_query.c.version_display,
latest_query.c.classifiers,
latest_query.c.created,
Expand Down Expand Up @@ -374,6 +379,7 @@ def get_service_stmt(
services_meta_data.c.description,
services_meta_data.c.description_ui,
services_meta_data.c.thumbnail,
services_meta_data.c.icon,
services_meta_data.c.version_display,
# ownership
owner_subquery.label("owner_email"),
Expand Down
Loading
Loading