Skip to content

Commit 2e9c48f

Browse files
authored
Merge branch 'master' into is23/web-api-list-users-for-admin
2 parents 726b768 + 1d5454a commit 2e9c48f

File tree

58 files changed

+2652
-1307
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+2652
-1307
lines changed

api/specs/web-server/_computations.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,17 @@
66
from models_library.api_schemas_webserver.computations import (
77
ComputationGet,
88
ComputationPathParams,
9+
ComputationRunIterationsLatestListQueryParams,
10+
ComputationRunIterationsListQueryParams,
911
ComputationRunPathParams,
1012
ComputationRunRestGet,
11-
ComputationRunWithFiltersListQueryParams,
1213
ComputationStart,
1314
ComputationStarted,
1415
ComputationTaskRestGet,
1516
)
1617
from models_library.generics import Envelope
1718
from simcore_service_webserver._meta import API_VTAG
1819
from simcore_service_webserver.director_v2._controller.computations_rest import (
19-
ComputationRunListQueryParams,
2020
ComputationTaskListQueryParams,
2121
ComputationTaskPathParams,
2222
)
@@ -71,7 +71,9 @@ async def stop_computation(_path: Annotated[ComputationPathParams, Depends()]):
7171
response_model=Page[ComputationRunRestGet],
7272
)
7373
async def list_computations_latest_iteration(
74-
_query: Annotated[as_query(ComputationRunWithFiltersListQueryParams), Depends()],
74+
_query: Annotated[
75+
as_query(ComputationRunIterationsLatestListQueryParams), Depends()
76+
],
7577
): ...
7678

7779

@@ -80,7 +82,7 @@ async def list_computations_latest_iteration(
8082
response_model=Page[ComputationRunRestGet],
8183
)
8284
async def list_computation_iterations(
83-
_query: Annotated[as_query(ComputationRunListQueryParams), Depends()],
85+
_query: Annotated[as_query(ComputationRunIterationsListQueryParams), Depends()],
8486
_path: Annotated[ComputationRunPathParams, Depends()],
8587
): ...
8688

packages/dask-task-models-library/src/dask_task_models_library/container_tasks/io.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,9 @@ def from_task_output(
195195
with suppress(json.JSONDecodeError):
196196
# NOTE: The suppression here is ok, since if the data is empty,
197197
# there will be a validation error anyway
198-
data = json_loads(output_data_file.read_text())
198+
loaded_data = json_loads(output_data_file.read_text())
199+
# ignore what is not in the schema
200+
data = {k: v for k, v in loaded_data.items() if k in schema}
199201

200202
for output_key, output_params in schema.items():
201203
if isinstance(output_params, FilePortSchema):

packages/dask-task-models-library/tests/container_tasks/test_io.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,39 @@ def test_objects_are_compatible_with_dask_requirements(model_cls, model_cls_exam
184184
model_instance = model_cls.model_validate(example)
185185
reloaded_instance = loads(dumps(model_instance))
186186
assert reloaded_instance == model_instance
187+
188+
189+
def test_create_task_output_from_task_ignores_additional_entries(
190+
tmp_path: Path, faker: Faker
191+
):
192+
task_output_schema = TaskOutputDataSchema.model_validate(
193+
{
194+
"some_output_1": {
195+
"required": True,
196+
},
197+
"some_output_2": {
198+
"required": True,
199+
},
200+
}
201+
)
202+
output_file = _create_fake_outputs(task_output_schema, tmp_path, False, faker)
203+
assert output_file
204+
# Add more data to the output file to simulate additional entries
205+
file_path = tmp_path / output_file
206+
data = json.loads(file_path.read_text())
207+
# Ensure the file contains the expected keys first
208+
for key in task_output_schema:
209+
assert key in data
210+
# Add an extra key
211+
data["extra_key"] = "extra_value"
212+
file_path.write_text(json.dumps(data))
213+
214+
task_output_data = TaskOutputData.from_task_output(
215+
schema=task_output_schema,
216+
output_folder=tmp_path,
217+
output_file_ext=output_file,
218+
)
219+
# Only keys defined in the schema should be present
220+
assert set(task_output_data.keys()) == set(
221+
task_output_schema.keys()
222+
), "Should only contain the expected keys"

packages/models-library/src/models_library/api_schemas_catalog/services.py

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -172,23 +172,57 @@ def _update_json_schema_extra(schema: JsonDict) -> None:
172172
)
173173

174174

175-
class _BaseServiceGetV2(CatalogOutputSchema):
176-
# Model used in catalog's rpc and rest interfaces
175+
class ServiceSummary(CatalogOutputSchema):
177176
key: ServiceKey
178177
version: ServiceVersion
179-
180178
name: str
181-
thumbnail: HttpUrl | None = None
182-
icon: HttpUrl | None = None
183179
description: str
184180

185-
description_ui: bool = False
186-
187181
version_display: str | None = None
188182

183+
contact: LowerCaseEmailStr | None
184+
185+
@staticmethod
186+
def _update_json_schema_extra(schema: JsonDict) -> None:
187+
schema.update(
188+
{
189+
"examples": [
190+
{
191+
"key": _EXAMPLE_SLEEPER["key"],
192+
"version": _EXAMPLE_SLEEPER["version"],
193+
"name": _EXAMPLE_SLEEPER["name"],
194+
"description": _EXAMPLE_SLEEPER["description"],
195+
"version_display": _EXAMPLE_SLEEPER["version_display"],
196+
"contact": _EXAMPLE_SLEEPER["contact"],
197+
},
198+
{
199+
"key": _EXAMPLE_FILEPICKER["key"],
200+
"version": _EXAMPLE_FILEPICKER["version"],
201+
"name": _EXAMPLE_FILEPICKER["name"],
202+
"description": _EXAMPLE_FILEPICKER["description"],
203+
"version_display": None,
204+
"contact": _EXAMPLE_FILEPICKER["contact"],
205+
},
206+
]
207+
}
208+
)
209+
210+
model_config = ConfigDict(
211+
extra="ignore",
212+
populate_by_name=True,
213+
alias_generator=snake_to_camel,
214+
json_schema_extra=_update_json_schema_extra,
215+
)
216+
217+
218+
class _BaseServiceGetV2(ServiceSummary):
189219
service_type: Annotated[ServiceType, Field(alias="type")]
190220

191-
contact: LowerCaseEmailStr | None
221+
thumbnail: HttpUrl | None = None
222+
icon: HttpUrl | None = None
223+
224+
description_ui: bool = False
225+
192226
authors: Annotated[list[Author], Field(min_length=1)]
193227
owner: Annotated[
194228
LowerCaseEmailStr | None,
@@ -217,6 +251,7 @@ class _BaseServiceGetV2(CatalogOutputSchema):
217251
extra="forbid",
218252
populate_by_name=True,
219253
alias_generator=snake_to_camel,
254+
json_schema_extra={"example": _EXAMPLE_SLEEPER},
220255
)
221256

222257

@@ -249,7 +284,6 @@ def _update_json_schema_extra(schema: JsonDict) -> None:
249284

250285

251286
class ServiceGetV2(_BaseServiceGetV2):
252-
# Model used in catalog's rpc and rest interfaces
253287
history: Annotated[
254288
list[ServiceRelease],
255289
Field(
@@ -338,6 +372,9 @@ def _update_json_schema_extra(schema: JsonDict) -> None:
338372
ServiceRelease
339373
]
340374

375+
# Create PageRpc types
376+
PageRpcServiceSummary = PageRpc[ServiceSummary]
377+
341378
ServiceResourcesGet: TypeAlias = ServiceResourcesDict
342379

343380

packages/models-library/src/models_library/api_schemas_webserver/computations.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,20 @@ class ComputationRunListQueryParams(
8484
): ...
8585

8686

87-
class ComputationRunWithFiltersListQueryParams(ComputationRunListQueryParams):
87+
class ComputationRunIterationsLatestListQueryParams(ComputationRunListQueryParams):
8888
filter_only_running: bool = Field(
8989
default=False,
9090
description="If true, only running computations are returned",
9191
)
9292

9393

94+
class ComputationRunIterationsListQueryParams(ComputationRunListQueryParams):
95+
include_children: bool = Field(
96+
default=False,
97+
description="If true, all computational runs of the project and its children are returned (Currently supported only for root projects)",
98+
)
99+
100+
94101
class ComputationRunRestGet(OutputSchema):
95102
project_uuid: ProjectID
96103
iteration: int
@@ -128,7 +135,11 @@ class ComputationTaskPathParams(BaseModel):
128135
class ComputationTaskListQueryParams(
129136
PageQueryParameters,
130137
ComputationTaskListOrderParams, # type: ignore[misc, valid-type]
131-
): ...
138+
):
139+
include_children: bool = Field(
140+
default=False,
141+
description="If true, all tasks of the project and its children are returned (Currently supported only for root projects)",
142+
)
132143

133144

134145
class ComputationTaskRestGet(OutputSchema):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"""add index to projects_metadata
2+
3+
Revision ID: 4e7d8719855b
4+
Revises: ba9c4816a31b
5+
Create Date: 2025-05-21 11:48:34.062860+00:00
6+
7+
"""
8+
9+
from alembic import op
10+
11+
# revision identifiers, used by Alembic.
12+
revision = "4e7d8719855b"
13+
down_revision = "ba9c4816a31b"
14+
branch_labels = None
15+
depends_on = None
16+
17+
18+
def upgrade():
19+
# ### commands auto generated by Alembic - please adjust! ###
20+
op.create_index(
21+
"idx_projects_metadata_root_parent_project_uuid",
22+
"projects_metadata",
23+
["root_parent_project_uuid"],
24+
unique=False,
25+
)
26+
# ### end Alembic commands ###
27+
28+
29+
def downgrade():
30+
# ### commands auto generated by Alembic - please adjust! ###
31+
op.drop_index(
32+
"idx_projects_metadata_root_parent_project_uuid", table_name="projects_metadata"
33+
)
34+
# ### end Alembic commands ###

packages/postgres-database/src/simcore_postgres_database/models/projects_metadata.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""
2-
These tables were designed to be controled by projects-plugin in
3-
the webserver's service
2+
These tables were designed to be controled by projects-plugin in
3+
the webserver's service
44
"""
55

66
import sqlalchemy as sa
@@ -100,6 +100,10 @@
100100
ondelete=RefActions.SET_NULL,
101101
name="fk_projects_metadata_root_parent_node_id",
102102
),
103+
#######
104+
sa.Index(
105+
"idx_projects_metadata_root_parent_project_uuid", "root_parent_project_uuid"
106+
),
103107
)
104108

105109

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

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
LatestServiceGet,
1414
ServiceGetV2,
1515
ServiceListFilters,
16+
ServiceSummary,
1617
ServiceUpdateV2,
1718
)
1819
from models_library.api_schemas_catalog.services_ports import ServicePortGet
@@ -200,6 +201,66 @@ async def get_service_ports(
200201
ServicePortGet.model_json_schema()["examples"],
201202
)
202203

204+
@validate_call(config={"arbitrary_types_allowed": True})
205+
async def list_all_services_summaries_paginated(
206+
self,
207+
rpc_client: RabbitMQRPCClient | MockType,
208+
*,
209+
product_name: ProductName,
210+
user_id: UserID,
211+
limit: PageLimitInt,
212+
offset: NonNegativeInt,
213+
filters: ServiceListFilters | None = None,
214+
):
215+
assert rpc_client
216+
assert product_name
217+
assert user_id
218+
219+
service_summaries = TypeAdapter(list[ServiceSummary]).validate_python(
220+
ServiceSummary.model_json_schema()["examples"],
221+
)
222+
if filters:
223+
filtered_summaries = []
224+
for summary in service_summaries:
225+
# Match service type if specified
226+
if (
227+
filters.service_type
228+
and {
229+
ServiceType.COMPUTATIONAL: "/comp/",
230+
ServiceType.DYNAMIC: "/dynamic/",
231+
}[filters.service_type]
232+
not in summary.key
233+
):
234+
continue
235+
236+
# Match service key pattern if specified
237+
if filters.service_key_pattern and not fnmatch.fnmatch(
238+
summary.key, filters.service_key_pattern
239+
):
240+
continue
241+
242+
# Match version display pattern if specified
243+
if filters.version_display_pattern and (
244+
summary.version_display is None
245+
or not fnmatch.fnmatch(
246+
summary.version_display, filters.version_display_pattern
247+
)
248+
):
249+
continue
250+
251+
filtered_summaries.append(summary)
252+
253+
service_summaries = filtered_summaries
254+
255+
total_count = len(service_summaries)
256+
257+
return PageRpc[ServiceSummary].create(
258+
service_summaries[offset : offset + limit],
259+
total=total_count,
260+
limit=limit,
261+
offset=offset,
262+
)
263+
203264

204265
@dataclass
205266
class ZeroListingCatalogRpcSideEffects:
@@ -216,3 +277,11 @@ async def list_my_service_history_latest_first(self, *args, **kwargs):
216277
limit=10,
217278
offset=0,
218279
)
280+
281+
async def list_all_services_summaries_paginated(self, *args, **kwargs):
282+
return PageRpc[ServiceSummary].create(
283+
[],
284+
total=0,
285+
limit=10,
286+
offset=0,
287+
)

0 commit comments

Comments
 (0)