Skip to content

Commit 9805e23

Browse files
committed
feat: add ServicePort model and get_service_ports function for handling service inputs and outputs
1 parent ff01ad7 commit 9805e23

File tree

5 files changed

+124
-14
lines changed

5 files changed

+124
-14
lines changed

packages/models-library/src/models_library/services_io.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ class BaseServiceIOModel(BaseModel):
3030
Base class for service input/outputs
3131
"""
3232

33-
## management
34-
3533
### human readable descriptors
3634
display_order: float | None = Field(
3735
None,

services/catalog/src/simcore_service_catalog/api/_dependencies/services.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,9 @@ async def get_service_from_manifest(
9595
return cast(
9696
ServiceMetaDataPublished,
9797
await manifest.get_service(
98+
director_client=director_client,
9899
key=service_key,
99100
version=service_version,
100-
director_client=director_client,
101101
),
102102
)
103103

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from typing import Annotated, Literal
2+
3+
from models_library.services_io import ServiceInput, ServiceOutput
4+
from pydantic import BaseModel, Field
5+
6+
7+
class ServicePort(BaseModel):
8+
kind: Annotated[
9+
Literal["input", "output"],
10+
Field(description="Whether this is an input or output port"),
11+
]
12+
key: Annotated[
13+
str, Field(description="The unique identifier for this port within the service")
14+
]
15+
port: ServiceInput | ServiceOutput

services/catalog/src/simcore_service_catalog/service/manifest.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636

3737
from .._constants import DIRECTOR_CACHING_TTL
3838
from ..clients.director import DirectorClient
39+
from ..models.services_ports import ServicePort
3940
from .function_services import get_function_service, is_function_service
4041

4142
_logger = logging.getLogger(__name__)
@@ -123,3 +124,40 @@ async def get_batch_services(
123124
tasks_group_prefix="manifest.get_batch_services",
124125
)
125126
return batch
127+
128+
129+
async def get_service_ports(
130+
director_client: DirectorClient,
131+
*,
132+
key: ServiceKey,
133+
version: ServiceVersion,
134+
) -> list[ServicePort]:
135+
"""Retrieves all ports (inputs and outputs) from a service"""
136+
ports = []
137+
service = await get_service(
138+
director_client=director_client,
139+
key=key,
140+
version=version,
141+
)
142+
143+
if service.inputs:
144+
for input_name, service_input in service.inputs.items():
145+
ports.append(
146+
ServicePort(
147+
kind="input",
148+
key=input_name,
149+
port=service_input,
150+
)
151+
)
152+
153+
if service.outputs:
154+
for output_name, service_output in service.outputs.items():
155+
ports.append(
156+
ServicePort(
157+
kind="output",
158+
key=output_name,
159+
port=service_output,
160+
)
161+
)
162+
163+
return ports

services/catalog/tests/unit/test_service_manifest.py

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,31 @@ def app_environment(
3131
)
3232

3333

34-
async def test_services_manifest_api(
34+
@pytest.fixture
35+
async def director_client(
3536
repository_lifespan_disabled: None,
3637
rabbitmq_and_rpc_setup_disabled: None,
3738
mocked_director_rest_api: MockRouter,
3839
app: FastAPI,
39-
):
40-
director_api = get_director_client(app)
40+
) -> DirectorClient:
41+
_client = get_director_client(app)
42+
assert app.state.director_api == _client
43+
assert isinstance(_client, DirectorClient)
44+
return _client
4145

42-
assert app.state.director_api == director_api
43-
assert isinstance(director_api, DirectorClient)
4446

45-
# LIST
46-
all_services_map = await manifest.get_services_map(director_api)
47+
@pytest.fixture
48+
async def all_services_map(
49+
director_client: DirectorClient,
50+
) -> manifest.ServiceMetaDataPublishedDict:
51+
return await manifest.get_services_map(director_client)
52+
53+
54+
async def test_get_services_map(
55+
mocked_director_rest_api: MockRouter,
56+
director_client: DirectorClient,
57+
):
58+
all_services_map = await manifest.get_services_map(director_client)
4759
assert mocked_director_rest_api["list_services"].called
4860

4961
for service in all_services_map.values():
@@ -57,22 +69,69 @@ async def test_services_manifest_api(
5769
}
5870
assert len(services_image_digest) < len(all_services_map)
5971

60-
# GET
72+
73+
async def test_get_service(
74+
mocked_director_rest_api: MockRouter,
75+
director_client: DirectorClient,
76+
all_services_map: manifest.ServiceMetaDataPublishedDict,
77+
):
78+
6179
for expected_service in all_services_map.values():
6280
service = await manifest.get_service(
6381
key=expected_service.key,
6482
version=expected_service.version,
65-
director_client=director_api,
83+
director_client=director_client,
6684
)
6785

6886
assert service == expected_service
6987
if not is_function_service(service.key):
7088
assert mocked_director_rest_api["get_service"].called
7189

72-
# BATCH
90+
91+
async def test_get_service_ports(
92+
director_client: DirectorClient,
93+
all_services_map: manifest.ServiceMetaDataPublishedDict,
94+
):
95+
96+
for expected_service in all_services_map.values():
97+
ports = await manifest.get_service_ports(
98+
key=expected_service.key,
99+
version=expected_service.version,
100+
director_client=director_client,
101+
)
102+
103+
# Verify all ports are properly retrieved
104+
assert isinstance(ports, list)
105+
106+
# Check input ports
107+
input_ports = [p for p in ports if p.kind == "input"]
108+
if expected_service.inputs:
109+
assert len(input_ports) == len(expected_service.inputs)
110+
for port in input_ports:
111+
assert port.key in expected_service.inputs
112+
assert port.port == expected_service.inputs[port.key]
113+
else:
114+
assert not input_ports
115+
116+
# Check output ports
117+
output_ports = [p for p in ports if p.kind == "output"]
118+
if expected_service.outputs:
119+
assert len(output_ports) == len(expected_service.outputs)
120+
for port in output_ports:
121+
assert port.key in expected_service.outputs
122+
assert port.port == expected_service.outputs[port.key]
123+
else:
124+
assert not output_ports
125+
126+
127+
async def test_get_batch_services(
128+
director_client: DirectorClient,
129+
all_services_map: manifest.ServiceMetaDataPublishedDict,
130+
):
131+
73132
for expected_services in toolz.partition(2, all_services_map.values()):
74133
selection = [(s.key, s.version) for s in expected_services]
75-
got_services = await manifest.get_batch_services(selection, director_api)
134+
got_services = await manifest.get_batch_services(selection, director_client)
76135

77136
assert [(s.key, s.version) for s in got_services] == selection
78137

0 commit comments

Comments
 (0)