Skip to content

Commit e0f8d57

Browse files
authored
Merge branch 'master' into 2025/upgrade/postgres
2 parents 80a81d7 + 8bda78f commit e0f8d57

File tree

31 files changed

+334
-229
lines changed

31 files changed

+334
-229
lines changed

services/catalog/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.6.0
1+
0.7.0

services/catalog/openapi.json

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"info": {
44
"title": "simcore-service-catalog",
55
"description": "Manages and maintains a catalog of all published components (e.g. macro-algorithms, scripts, etc)",
6-
"version": "0.6.0"
6+
"version": "0.7.0"
77
},
88
"paths": {
99
"/": {
@@ -136,6 +136,60 @@
136136
}
137137
}
138138
},
139+
"/v0/services/{service_key}/{service_version}/labels": {
140+
"get": {
141+
"tags": [
142+
"services"
143+
],
144+
"summary": "Get Service Labels",
145+
"operationId": "get_service_labels_v0_services__service_key___service_version__labels_get",
146+
"parameters": [
147+
{
148+
"name": "service_key",
149+
"in": "path",
150+
"required": true,
151+
"schema": {
152+
"type": "string",
153+
"pattern": "^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$",
154+
"title": "Service Key"
155+
}
156+
},
157+
{
158+
"name": "service_version",
159+
"in": "path",
160+
"required": true,
161+
"schema": {
162+
"type": "string",
163+
"pattern": "^(0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){2}(-(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*)(\\.(0|[1-9]\\d*|\\d*[-a-zA-Z][-\\da-zA-Z]*))*)?(\\+[-\\da-zA-Z]+(\\.[-\\da-zA-Z-]+)*)?$",
164+
"title": "Service Version"
165+
}
166+
}
167+
],
168+
"responses": {
169+
"200": {
170+
"description": "Successful Response",
171+
"content": {
172+
"application/json": {
173+
"schema": {
174+
"type": "object",
175+
"title": "Response Get Service Labels V0 Services Service Key Service Version Labels Get"
176+
}
177+
}
178+
}
179+
},
180+
"422": {
181+
"description": "Validation Error",
182+
"content": {
183+
"application/json": {
184+
"schema": {
185+
"$ref": "#/components/schemas/HTTPValidationError"
186+
}
187+
}
188+
}
189+
}
190+
}
191+
}
192+
},
139193
"/v0/services/{service_key}/{service_version}/specifications": {
140194
"get": {
141195
"tags": [

services/catalog/setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 0.6.0
2+
current_version = 0.7.0
33
commit = True
44
message = services/catalog version: {current_version} → {new_version}
55
tag = False
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from typing import Annotated, Any
2+
3+
from fastapi import APIRouter, Depends
4+
from models_library.services import ServiceKey, ServiceVersion
5+
6+
from ...services.director import DirectorApi
7+
from ..dependencies.director import get_director_api
8+
9+
router = APIRouter()
10+
11+
12+
@router.get("/{service_key:path}/{service_version}/labels")
13+
async def get_service_labels(
14+
service_key: ServiceKey,
15+
service_version: ServiceVersion,
16+
director_client: Annotated[DirectorApi, Depends(get_director_api)],
17+
) -> dict[str, Any]:
18+
return await director_client.get_service_labels(service_key, service_version)

services/catalog/src/simcore_service_catalog/api/rest/_services_resources.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import logging
22
import urllib.parse
33
from copy import deepcopy
4-
from typing import Annotated, Any, Final, cast
4+
from typing import Annotated, Any, Final
55

66
import yaml
77
from fastapi import APIRouter, Depends, HTTPException, status
@@ -131,12 +131,7 @@ async def _get_service_labels(
131131
director_client: DirectorApi, key: ServiceKey, version: ServiceVersion
132132
) -> dict[str, Any] | None:
133133
try:
134-
service_labels = cast(
135-
dict[str, Any],
136-
await director_client.get(
137-
f"/services/{urllib.parse.quote_plus(key)}/{version}/labels"
138-
),
139-
)
134+
service_labels = await director_client.get_service_labels(key, version)
140135
_logger.debug(
141136
"received for %s %s",
142137
f"/services/{urllib.parse.quote_plus(key)}/{version}/labels",

services/catalog/src/simcore_service_catalog/api/rest/routes.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
_meta,
88
_services,
99
_services_access_rights,
10+
_services_labels,
1011
_services_ports,
1112
_services_resources,
1213
_services_specifications,
@@ -38,6 +39,11 @@
3839
tags=_SERVICE_TAGS,
3940
prefix=_SERVICE_PREFIX,
4041
)
42+
v0_router.include_router(
43+
_services_labels.router,
44+
tags=_SERVICE_TAGS,
45+
prefix=_SERVICE_PREFIX,
46+
)
4147
v0_router.include_router(
4248
_services_specifications.router,
4349
tags=_SERVICE_TAGS,

services/catalog/src/simcore_service_catalog/services/director.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,17 @@ async def get_service(
156156
assert len(data) == 1 # nosec
157157
return ServiceMetaDataPublished.model_validate(data[0])
158158

159+
async def get_service_labels(
160+
self,
161+
service_key: ServiceKey,
162+
service_version: ServiceVersion,
163+
) -> dict[str, Any]:
164+
response = await self.get(
165+
f"/services/{urllib.parse.quote_plus(service_key)}/{service_version}/labels"
166+
)
167+
assert isinstance(response, dict) # nosec
168+
return response
169+
159170

160171
async def setup_director(
161172
app: FastAPI, tracing_settings: TracingSettings | None

services/catalog/tests/unit/conftest.py

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -393,11 +393,41 @@ def mocked_director_service_api_base(
393393
yield respx_mock
394394

395395

396+
@pytest.fixture
397+
def get_mocked_service_labels() -> Callable[[str, str], dict]:
398+
def _(service_key: str, service_version: str) -> dict:
399+
return {
400+
"io.simcore.authors": '{"authors": [{"name": "John Smith", "email": "[email protected]", "affiliation": "ACME\'IS Foundation"}]}',
401+
"io.simcore.contact": '{"contact": "[email protected]"}',
402+
"io.simcore.description": '{"description": "Autonomous Nervous System Network model"}',
403+
"io.simcore.inputs": '{"inputs": {"input_1": {"displayOrder": 1.0, "label": "Simulation time", "description": "Duration of the simulation", "type": "ref_contentSchema", "contentSchema": {"type": "number", "x_unit": "milli-second"}, "defaultValue": 2.0}}}',
404+
"io.simcore.integration-version": '{"integration-version": "1.0.0"}',
405+
"io.simcore.key": '{"key": "xxxxx"}'.replace("xxxxx", service_key),
406+
"io.simcore.name": '{"name": "Autonomous Nervous System Network model"}',
407+
"io.simcore.outputs": '{"outputs": {"output_1": {"displayOrder": 1.0, "label": "ANS output", "description": "Output of simulation of Autonomous Nervous System Network model", "type": "data:*/*", "fileToKeyMap": {"ANS_output.txt": "output_1"}}, "output_2": {"displayOrder": 2.0, "label": "Stimulation parameters", "description": "stim_param.txt file containing the input provided in the inputs port", "type": "data:*/*", "fileToKeyMap": {"ANS_stim_param.txt": "output_2"}}}}',
408+
"io.simcore.thumbnail": '{"thumbnail": "https://www.statnews.com/wp-content/uploads/2020/05/3D-rat-heart.-iScience--768x432.png"}',
409+
"io.simcore.type": '{"type": "computational"}',
410+
"io.simcore.version": '{"version": "xxxxx"}'.replace(
411+
"xxxxx", service_version
412+
),
413+
"maintainer": "johnsmith",
414+
"org.label-schema.build-date": "2023-04-17T08:04:15Z",
415+
"org.label-schema.schema-version": "1.0",
416+
"org.label-schema.vcs-ref": "",
417+
"org.label-schema.vcs-url": "",
418+
"simcore.service.restart-policy": "no-restart",
419+
"simcore.service.settings": '[{"name": "Resources", "type": "Resources", "value": {"Limits": {"NanoCPUs": 4000000000, "MemoryBytes": 2147483648}, "Reservations": {"NanoCPUs": 4000000000, "MemoryBytes": 2147483648}}}]',
420+
}
421+
422+
return _
423+
424+
396425
@pytest.fixture
397426
def mocked_director_service_api(
398427
mocked_director_service_api_base: respx.MockRouter,
399428
director_service_openapi_specs: dict[str, Any],
400429
expected_director_list_services: list[dict[str, Any]],
430+
get_mocked_service_labels: Callable[[str, str], dict],
401431
) -> respx.MockRouter:
402432
"""
403433
STANDARD fixture to mock director service API
@@ -461,30 +491,7 @@ def _get_service_labels(request, service_key, service_version):
461491
return httpx.Response(
462492
status_code=status.HTTP_200_OK,
463493
json={
464-
"data": {
465-
"io.simcore.authors": '{"authors": [{"name": "John Smith", "email": "[email protected]", "affiliation": "ACME\'IS Foundation"}]}',
466-
"io.simcore.contact": '{"contact": "[email protected]"}',
467-
"io.simcore.description": '{"description": "Autonomous Nervous System Network model"}',
468-
"io.simcore.inputs": '{"inputs": {"input_1": {"displayOrder": 1.0, "label": "Simulation time", "description": "Duration of the simulation", "type": "ref_contentSchema", "contentSchema": {"type": "number", "x_unit": "milli-second"}, "defaultValue": 2.0}}}',
469-
"io.simcore.integration-version": '{"integration-version": "1.0.0"}',
470-
"io.simcore.key": '{"key": "xxxxx"}'.replace(
471-
"xxxxx", found["key"]
472-
),
473-
"io.simcore.name": '{"name": "Autonomous Nervous System Network model"}',
474-
"io.simcore.outputs": '{"outputs": {"output_1": {"displayOrder": 1.0, "label": "ANS output", "description": "Output of simulation of Autonomous Nervous System Network model", "type": "data:*/*", "fileToKeyMap": {"ANS_output.txt": "output_1"}}, "output_2": {"displayOrder": 2.0, "label": "Stimulation parameters", "description": "stim_param.txt file containing the input provided in the inputs port", "type": "data:*/*", "fileToKeyMap": {"ANS_stim_param.txt": "output_2"}}}}',
475-
"io.simcore.thumbnail": '{"thumbnail": "https://www.statnews.com/wp-content/uploads/2020/05/3D-rat-heart.-iScience--768x432.png"}',
476-
"io.simcore.type": '{"type": "computational"}',
477-
"io.simcore.version": '{"version": "xxxxx"}'.replace(
478-
"xxxxx", found["version"]
479-
),
480-
"maintainer": "iavarone",
481-
"org.label-schema.build-date": "2023-04-17T08:04:15Z",
482-
"org.label-schema.schema-version": "1.0",
483-
"org.label-schema.vcs-ref": "",
484-
"org.label-schema.vcs-url": "",
485-
"simcore.service.restart-policy": "no-restart",
486-
"simcore.service.settings": '[{"name": "Resources", "type": "Resources", "value": {"Limits": {"NanoCPUs": 4000000000, "MemoryBytes": 2147483648}, "Reservations": {"NanoCPUs": 4000000000, "MemoryBytes": 2147483648}}}]',
487-
}
494+
"data": get_mocked_service_labels(found["key"], found["version"])
488495
},
489496
)
490497
return httpx.Response(
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# pylint: disable=redefined-outer-name
2+
# pylint: disable=unused-argument
3+
4+
from collections.abc import Callable
5+
from unittest.mock import AsyncMock
6+
7+
import pytest
8+
from fastapi import FastAPI
9+
from httpx import AsyncClient
10+
from respx import MockRouter
11+
12+
13+
@pytest.fixture
14+
def mock_engine(app: FastAPI) -> None:
15+
app.state.engine = AsyncMock()
16+
17+
18+
async def test_get_service_labels(
19+
postgres_setup_disabled: None,
20+
mocked_director_service_api: MockRouter,
21+
rabbitmq_and_rpc_setup_disabled: None,
22+
background_tasks_setup_disabled: None,
23+
mock_engine: None,
24+
get_mocked_service_labels: Callable[[str, str], dict],
25+
aclient: AsyncClient,
26+
):
27+
service_key = "simcore/services/comp/ans-model"
28+
service_version = "3.0.0"
29+
result = await aclient.get(f"/v0/services/{service_key}/{service_version}/labels")
30+
assert result.status_code == 200, result.text
31+
assert result.json() == get_mocked_service_labels(service_key, service_version)

services/catalog/tests/unit/with_dbs/test_api_rest_services_resources.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ class _ServiceResourceParams:
186186
],
187187
)
188188
async def test_get_service_resources(
189-
background_tasks_setup_disabled,
189+
background_tasks_setup_disabled: None,
190190
rabbitmq_and_rpc_setup_disabled: None,
191191
mocked_director_service_labels: Route,
192192
client: TestClient,
@@ -298,7 +298,7 @@ def factory(services_labels: dict[str, dict[str, Any]]) -> None:
298298
],
299299
)
300300
async def test_get_service_resources_sim4life_case(
301-
background_tasks_setup_disabled,
301+
background_tasks_setup_disabled: None,
302302
rabbitmq_and_rpc_setup_disabled: None,
303303
create_mock_director_service_labels: Callable,
304304
client: TestClient,
@@ -319,7 +319,7 @@ async def test_get_service_resources_sim4life_case(
319319

320320

321321
async def test_get_service_resources_raises_errors(
322-
background_tasks_setup_disabled,
322+
background_tasks_setup_disabled: None,
323323
rabbitmq_and_rpc_setup_disabled: None,
324324
mocked_director_service_labels: Route,
325325
client: TestClient,

0 commit comments

Comments
 (0)