Skip to content

Commit 0c23386

Browse files
authored
🎨 web-api services api response includes manifest info (part 5) (#6061)
1 parent 9e7cf24 commit 0c23386

File tree

15 files changed

+356
-166
lines changed

15 files changed

+356
-166
lines changed

packages/pytest-simcore/src/pytest_simcore/docker_compose.py

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -167,20 +167,13 @@ def simcore_docker_compose(
167167
docker_compose_path.exists() for docker_compose_path in docker_compose_paths
168168
)
169169

170-
compose_specs = run_docker_compose_config(
170+
return run_docker_compose_config(
171171
project_dir=osparc_simcore_root_dir / "services",
172172
scripts_dir=osparc_simcore_scripts_dir,
173173
docker_compose_paths=docker_compose_paths,
174174
env_file_path=env_file_for_testing,
175175
destination_path=temp_folder / "simcore_docker_compose.yml",
176176
)
177-
# NOTE: do not add indent. Copy&Paste log into editor instead
178-
print(
179-
HEADER_STR.format("simcore docker-compose"),
180-
json.dumps(compose_specs),
181-
HEADER_STR.format("-"),
182-
)
183-
return compose_specs
184177

185178

186179
@pytest.fixture(scope="module")
@@ -203,20 +196,13 @@ def ops_docker_compose(
203196
)
204197
assert docker_compose_path.exists()
205198

206-
compose_specs = run_docker_compose_config(
199+
return run_docker_compose_config(
207200
project_dir=osparc_simcore_root_dir / "services",
208201
scripts_dir=osparc_simcore_scripts_dir,
209202
docker_compose_paths=docker_compose_path,
210203
env_file_path=env_file_for_testing,
211204
destination_path=temp_folder / "ops_docker_compose.yml",
212205
)
213-
# NOTE: do not add indent. Copy&Paste log into editor instead
214-
print(
215-
HEADER_STR.format("ops docker-compose"),
216-
json.dumps(compose_specs),
217-
HEADER_STR.format("-"),
218-
)
219-
return compose_specs
220206

221207

222208
@pytest.fixture(scope="module")
@@ -245,6 +231,11 @@ def core_docker_compose_file(
245231
core_services_selection, simcore_docker_compose, docker_compose_path
246232
)
247233

234+
print(
235+
HEADER_STR.format(f"{docker_compose_path}"),
236+
json.dumps(docker_compose_path.read_text()),
237+
HEADER_STR.format("-"),
238+
)
248239
return docker_compose_path
249240

250241

@@ -281,6 +272,11 @@ def ops_docker_compose_file(
281272
ops_services_selection, ops_docker_compose, docker_compose_path
282273
)
283274

275+
print(
276+
HEADER_STR.format(f"{docker_compose_path}"),
277+
json.dumps(docker_compose_path.read_text()),
278+
HEADER_STR.format("-"),
279+
)
284280
return docker_compose_path
285281

286282

packages/pytest-simcore/src/pytest_simcore/helpers/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@
77

88

99
# string templates
10-
HEADER_STR: str = "{:-^50}\n"
10+
HEADER_STR: str = "{:-^100}\n"

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ async def get_service_from_manifest(
9393
"""
9494
try:
9595
return await manifest.get_service(
96-
service_key=service_key,
97-
service_version=service_version,
96+
key=service_key,
97+
version=service_version,
9898
director_client=director_client,
9999
)
100100

services/catalog/src/simcore_service_catalog/api/rpc/_services.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
)
2020

2121
from ...db.repositories.services import ServicesRepository
22-
from ...services import catalog
22+
from ...services import services_api
23+
from ..dependencies.director import get_director_api
2324

2425
_logger = logging.getLogger(__name__)
2526

@@ -38,8 +39,9 @@ async def list_services_paginated(
3839
) -> PageRpcServicesGetV2:
3940
assert app.state.engine # nosec
4041

41-
total_count, items = await catalog.list_services_paginated(
42+
total_count, items = await services_api.list_services_paginated(
4243
repo=ServicesRepository(app.state.engine),
44+
director_api=get_director_api(app),
4345
product_name=product_name,
4446
user_id=user_id,
4547
limit=limit,
@@ -69,8 +71,9 @@ async def get_service(
6971
) -> ServiceGetV2:
7072
assert app.state.engine # nosec
7173

72-
service = await catalog.get_service(
74+
service = await services_api.get_service(
7375
repo=ServicesRepository(app.state.engine),
76+
director_api=get_director_api(app),
7477
product_name=product_name,
7578
user_id=user_id,
7679
service_key=service_key,
@@ -98,8 +101,9 @@ async def update_service(
98101

99102
assert app.state.engine # nosec
100103

101-
service = await catalog.update_service(
104+
service = await services_api.update_service(
102105
repo=ServicesRepository(app.state.engine),
106+
director_api=get_director_api(app),
103107
product_name=product_name,
104108
user_id=user_id,
105109
service_key=service_key,

services/catalog/src/simcore_service_catalog/db/repositories/services.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -432,12 +432,9 @@ async def list_services_access_rights(
432432
)
433433
async with self.db_engine.connect() as conn:
434434
async for row in await conn.stream(query):
435-
service_to_access_rights[
436-
(
437-
row[services_access_rights.c.key],
438-
row[services_access_rights.c.version],
439-
)
440-
].append(ServiceAccessRightsAtDB.from_orm(row))
435+
service_to_access_rights[(row.key, row.version)].append(
436+
ServiceAccessRightsAtDB.from_orm(row)
437+
)
441438
return service_to_access_rights
442439

443440
async def upsert_service_access_rights(

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

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from models_library.services_metadata_published import ServiceMetaDataPublished
1212
from models_library.services_types import ServiceKey, ServiceVersion
1313
from models_library.utils.json_serialization import json_dumps
14-
from pydantic import parse_obj_as
1514
from servicelib.logging_utils import log_context
1615
from starlette import status
1716
from tenacity._asyncio import AsyncRetrying
@@ -140,12 +139,6 @@ async def is_responsive(self) -> bool:
140139
except (httpx.HTTPStatusError, httpx.RequestError, httpx.TimeoutException):
141140
return False
142141

143-
async def list_all_services(self) -> list[ServiceMetaDataPublished]:
144-
# WARNING: this function probably raise ValidationError since director does NOT offer guarantees.
145-
# SEE list_registered_services
146-
data = await self.get("/services")
147-
return parse_obj_as(list[ServiceMetaDataPublished], data)
148-
149142
async def get_service(
150143
self, service_key: ServiceKey, service_version: ServiceVersion
151144
) -> ServiceMetaDataPublished:

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

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@
2727
import logging
2828
from typing import Any, TypeAlias, cast
2929

30+
from aiocache import cached
3031
from models_library.function_services_catalog.api import iter_service_docker_data
3132
from models_library.services_metadata_published import ServiceMetaDataPublished
3233
from models_library.services_types import ServiceKey, ServiceVersion
3334
from pydantic import ValidationError
35+
from servicelib.utils import limited_gather
3436

37+
from .._constants import DIRECTOR_CACHING_TTL
3538
from .director import DirectorApi
3639
from .function_services import get_function_service, is_function_service
3740

@@ -80,18 +83,43 @@ async def get_services_map(
8083
return services
8184

8285

86+
@cached(
87+
ttl=DIRECTOR_CACHING_TTL,
88+
namespace=__name__,
89+
key_builder=lambda f, *ag, **kw: f"{f.__name__}/{kw['key']}/{kw['version']}",
90+
)
8391
async def get_service(
84-
service_key: ServiceKey,
85-
service_version: ServiceVersion,
8692
director_client: DirectorApi,
93+
*,
94+
key: ServiceKey,
95+
version: ServiceVersion,
8796
) -> ServiceMetaDataPublished:
8897
"""
8998
Retrieves service metadata from the docker registry via the director and accounting
99+
100+
raises if does not exist or if validation fails
90101
"""
91-
if is_function_service(service_key):
92-
service = get_function_service(key=service_key, version=service_version)
102+
if is_function_service(key):
103+
service = get_function_service(key=key, version=version)
93104
else:
94105
service = await director_client.get_service(
95-
service_key=service_key, service_version=service_version
106+
service_key=key, service_version=version
96107
)
97108
return service
109+
110+
111+
async def get_batch_services(
112+
selection: list[tuple[ServiceKey, ServiceVersion]],
113+
director_client: DirectorApi,
114+
) -> list[ServiceMetaDataPublished | BaseException]:
115+
116+
batch: list[ServiceMetaDataPublished | BaseException] = await limited_gather(
117+
*(
118+
get_service(key=k, version=v, director_client=director_client)
119+
for k, v in selection
120+
),
121+
reraise=False,
122+
log=_logger,
123+
tasks_group_prefix="manifest.get_batch_services",
124+
)
125+
return batch

services/catalog/src/simcore_service_catalog/services/catalog.py renamed to services/catalog/src/simcore_service_catalog/services/services_api.py

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
)
88
from models_library.products import ProductName
99
from models_library.rest_pagination import PageLimitInt
10-
from models_library.services_authoring import Author
1110
from models_library.services_enums import ServiceType
11+
from models_library.services_metadata_published import ServiceMetaDataPublished
1212
from models_library.services_types import ServiceKey, ServiceVersion
1313
from models_library.users import UserID
1414
from pydantic import NonNegativeInt
@@ -21,6 +21,8 @@
2121
ServiceMetaDataAtDB,
2222
ServiceWithHistoryFromDB,
2323
)
24+
from simcore_service_catalog.services import manifest
25+
from simcore_service_catalog.services.director import DirectorApi
2426

2527
from ..db.repositories.services import ServicesRepository
2628
from .function_services import is_function_service
@@ -39,29 +41,33 @@ def _deduce_service_type_from(key: str) -> ServiceType:
3941
def _db_to_api_model(
4042
service_db: ServiceWithHistoryFromDB,
4143
access_rights_db: list[ServiceAccessRightsAtDB],
44+
service_manifest: ServiceMetaDataPublished,
4245
) -> ServiceGetV2:
46+
assert (
47+
_deduce_service_type_from(service_db.key) == service_manifest.service_type
48+
) # nosec
4349
return ServiceGetV2(
4450
key=service_db.key,
4551
version=service_db.version,
4652
name=service_db.name,
4753
thumbnail=service_db.thumbnail or None,
4854
description=service_db.description,
49-
version_display=f"V{service_db.version}", # rg.version_display,
50-
type=_deduce_service_type_from(service_db.key), # rg.service_type,
51-
contact=Author.Config.schema_extra["examples"][0]["email"], # rg.contact,
52-
authors=Author.Config.schema_extra["examples"],
55+
version_display=service_manifest.version_display,
56+
type=service_manifest.service_type,
57+
contact=service_manifest.contact,
58+
authors=service_manifest.authors,
5359
owner=service_db.owner_email or None,
54-
inputs={}, # rg.inputs,
55-
outputs={}, # rg.outputs,
56-
boot_options=None, # rg.boot_options,
57-
min_visible_inputs=None, # rg.min_visible_inputs,
60+
inputs=service_manifest.inputs or {},
61+
outputs=service_manifest.outputs or {},
62+
boot_options=service_manifest.boot_options,
63+
min_visible_inputs=service_manifest.min_visible_inputs,
5864
access_rights={
5965
a.gid: ServiceGroupAccessRightsV2.construct(
6066
execute=a.execute_access,
6167
write=a.write_access,
6268
)
6369
for a in access_rights_db
64-
}, # db.access_rights,
70+
},
6571
classifiers=service_db.classifiers,
6672
quality=service_db.quality,
6773
history=[h.to_api_model() for h in service_db.history],
@@ -70,6 +76,7 @@ def _db_to_api_model(
7076

7177
async def list_services_paginated(
7278
repo: ServicesRepository,
79+
director_api: DirectorApi,
7380
product_name: ProductName,
7481
user_id: UserID,
7582
limit: PageLimitInt | None,
@@ -94,19 +101,31 @@ async def list_services_paginated(
94101
product_name=product_name,
95102
)
96103

104+
# get manifest of those with access rights
105+
got = await manifest.get_batch_services(
106+
[(s.key, s.version) for s in services if access_rights.get((s.key, s.version))],
107+
director_api,
108+
)
109+
service_manifest = {
110+
(s.key, s.version): s for s in got if isinstance(s, ServiceMetaDataPublished)
111+
}
112+
97113
# NOTE: aggregates published (i.e. not editable) is still missing in this version
98114
items = [
99-
_db_to_api_model(s, ar)
115+
_db_to_api_model(s, ar, sm)
100116
for s in services
101-
if (ar := access_rights.get((s.key, s.version)))
117+
if (
118+
(ar := access_rights.get((s.key, s.version)))
119+
and (sm := service_manifest.get((s.key, s.version)))
120+
)
102121
]
103122

104123
return total_count, items
105124

106125

107126
async def get_service(
108127
repo: ServicesRepository,
109-
# image_registry,
128+
director_api: DirectorApi,
110129
product_name: ProductName,
111130
user_id: UserID,
112131
service_key: ServiceKey,
@@ -142,11 +161,18 @@ async def get_service(
142161
product_name=product_name,
143162
)
144163

145-
return _db_to_api_model(service, access_rights)
164+
service_manifest = await manifest.get_service(
165+
key=service_key,
166+
version=service_version,
167+
director_client=director_api,
168+
)
169+
170+
return _db_to_api_model(service, access_rights, service_manifest)
146171

147172

148173
async def update_service(
149174
repo: ServicesRepository,
175+
director_api: DirectorApi,
150176
*,
151177
product_name: ProductName,
152178
user_id: UserID,
@@ -229,6 +255,7 @@ async def update_service(
229255

230256
return await get_service(
231257
repo=repo,
258+
director_api=director_api,
232259
product_name=product_name,
233260
user_id=user_id,
234261
service_key=service_key,

0 commit comments

Comments
 (0)