Skip to content

Commit 97993f2

Browse files
committed
refactor patch method
1 parent 7a68768 commit 97993f2

File tree

7 files changed

+144
-97
lines changed

7 files changed

+144
-97
lines changed

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,20 @@ class BatchCreateEnvelope(BaseModel, Generic[SchemaT]):
102102
description="List of successfully created items",
103103
),
104104
]
105+
106+
107+
class BatchUpdateEnvelope(BaseModel, Generic[SchemaT]):
108+
"""Generic envelope model for batch-update operations.
109+
110+
This model represents the result of a strict batch update operation,
111+
containing the list of updated items. The operation is expected to be "strict"
112+
in the sense that it either updates all requested items or fails entirely. See https://google.aip.dev/234
113+
"""
114+
115+
updated_items: Annotated[
116+
list[SchemaT],
117+
Field(
118+
min_length=1,
119+
description="List of successfully updated items",
120+
),
121+
]

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from models_library.utils.enums import StrAutoEnum
1616
from pydantic import BaseModel, ConfigDict, Field
1717

18+
from .batch_operations import BatchUpdateEnvelope
1819
from .projects import ProjectID
1920
from .utils.change_case import snake_to_camel
2021

@@ -273,8 +274,10 @@ class RegisteredPythonCodeFunctionJob(PythonCodeFunctionJob, RegisteredFunctionJ
273274

274275

275276
class BatchCreateRegisteredFunctionJobs(BatchCreateEnvelope[RegisteredFunctionJob]):
276-
"""Envelope model for batch registering function jobs"""
277+
pass
278+
277279

280+
class BatchUpdateRegisteredFunctionJobs(BatchUpdateEnvelope[RegisteredFunctionJob]):
278281
pass
279282

280283

@@ -291,6 +294,16 @@ class FunctionJobPatchRequest(BaseModel):
291294
patch: RegisteredFunctionJobPatch
292295

293296

297+
FunctionJobPatchRequestList: TypeAlias = Annotated[
298+
list[FunctionJobPatchRequest],
299+
Field(
300+
max_length=_MAX_LIST_LENGTH,
301+
min_length=_MIN_LIST_LENGTH,
302+
description="List of function job patch requests",
303+
),
304+
]
305+
306+
294307
class FunctionJobStatus(BaseModel):
295308
status: str
296309

@@ -363,6 +376,10 @@ class BatchCreateRegisteredFunctionJobsDB(BatchCreateEnvelope[RegisteredFunction
363376
pass
364377

365378

379+
class BatchUpdateRegisteredFunctionJobsDB(BatchUpdateEnvelope[RegisteredFunctionJobDB]):
380+
pass
381+
382+
366383
class RegisteredFunctionJobWithStatusDB(FunctionJobDB):
367384
uuid: FunctionJobID
368385
created: datetime.datetime

packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/v1/functions.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,19 @@
1818
)
1919
from models_library.functions import (
2020
BatchCreateRegisteredFunctionJobs,
21+
BatchUpdateRegisteredFunctionJobs,
2122
FunctionClass,
2223
FunctionGroupAccessRights,
2324
FunctionInputsList,
2425
FunctionJob,
2526
FunctionJobList,
27+
FunctionJobPatchRequest,
28+
FunctionJobPatchRequestList,
2629
FunctionJobStatus,
2730
FunctionOutputs,
2831
FunctionUserAccessRights,
2932
FunctionUserApiAccessRights,
3033
RegisteredFunctionJobWithStatus,
31-
RegisteredProjectFunctionJobPatchInputList,
32-
RegisteredSolverFunctionJobPatchInputList,
3334
)
3435
from models_library.products import ProductName
3536
from models_library.rabbitmq_basic_types import RPCNamespace
@@ -355,18 +356,32 @@ async def patch_registered_function_job(
355356
*,
356357
product_name: ProductName,
357358
user_id: UserID,
358-
registered_function_job_patch_inputs: (
359-
RegisteredProjectFunctionJobPatchInputList
360-
| RegisteredSolverFunctionJobPatchInputList
361-
),
362-
) -> list[RegisteredFunctionJob]:
359+
function_job_patch_request: FunctionJobPatchRequest,
360+
) -> RegisteredFunctionJob:
361+
"""Patch a registered function job."""
362+
return TypeAdapter(RegisteredFunctionJob).validate_python(
363+
await self._request(
364+
"patch_registered_function_job",
365+
product_name=product_name,
366+
user_id=user_id,
367+
function_job_patch_request=function_job_patch_request,
368+
),
369+
)
370+
371+
async def batch_patch_registered_function_job(
372+
self,
373+
*,
374+
product_name: ProductName,
375+
user_id: UserID,
376+
function_job_patch_requests: FunctionJobPatchRequestList,
377+
) -> BatchUpdateRegisteredFunctionJobs:
363378
"""Patch a registered function job."""
364-
return TypeAdapter(list[RegisteredFunctionJob]).validate_python(
379+
return BatchUpdateRegisteredFunctionJobs.model_validate(
365380
await self._request(
366381
"patch_registered_function_job",
367382
product_name=product_name,
368383
user_id=user_id,
369-
registered_function_job_patch_inputs=registered_function_job_patch_inputs,
384+
function_job_patch_requests=function_job_patch_requests,
370385
),
371386
)
372387

services/web/server/src/simcore_service_webserver/functions/_controller/_functions_rpc.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from aiohttp import web
44
from models_library.functions import (
55
BatchCreateRegisteredFunctionJobs,
6+
BatchUpdateRegisteredFunctionJobs,
67
Function,
78
FunctionAccessRights,
89
FunctionClass,
@@ -16,6 +17,8 @@
1617
FunctionJobCollectionsListFilters,
1718
FunctionJobID,
1819
FunctionJobList,
20+
FunctionJobPatchRequest,
21+
FunctionJobPatchRequestList,
1922
FunctionJobStatus,
2023
FunctionOutputs,
2124
FunctionOutputSchema,
@@ -25,8 +28,6 @@
2528
RegisteredFunctionJob,
2629
RegisteredFunctionJobCollection,
2730
RegisteredFunctionJobWithStatus,
28-
RegisteredProjectFunctionJobPatchInputList,
29-
RegisteredSolverFunctionJobPatchInputList,
3031
)
3132
from models_library.functions_errors import (
3233
FunctionIDNotFoundError,
@@ -133,17 +134,37 @@ async def patch_registered_function_job(
133134
*,
134135
user_id: UserID,
135136
product_name: ProductName,
136-
registered_function_job_patch_inputs: (
137-
RegisteredProjectFunctionJobPatchInputList
138-
| RegisteredSolverFunctionJobPatchInputList
139-
),
140-
) -> list[RegisteredFunctionJob]:
137+
function_job_patch_request: FunctionJobPatchRequest,
138+
) -> RegisteredFunctionJob:
141139

142140
return await _functions_service.patch_registered_function_job(
143141
app=app,
144142
user_id=user_id,
145143
product_name=product_name,
146-
registered_function_job_patch_inputs=registered_function_job_patch_inputs,
144+
function_job_patch_request=function_job_patch_request,
145+
)
146+
147+
148+
@router.expose(
149+
reraise_if_error_type=(
150+
UnsupportedFunctionJobClassError,
151+
FunctionJobsWriteApiAccessDeniedError,
152+
FunctionJobPatchModelIncompatibleError,
153+
)
154+
)
155+
async def batch_patch_registered_function_jobs(
156+
app: web.Application,
157+
*,
158+
user_id: UserID,
159+
product_name: ProductName,
160+
function_job_patch_requests: FunctionJobPatchRequestList,
161+
) -> BatchUpdateRegisteredFunctionJobs:
162+
163+
return await _functions_service.batch_patch_registered_function_jobs(
164+
app=app,
165+
user_id=user_id,
166+
product_name=product_name,
167+
function_job_patch_requests=function_job_patch_requests,
147168
)
148169

149170

services/web/server/src/simcore_service_webserver/functions/_function_jobs_repository.py

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,21 @@
77
from aiohttp import web
88
from models_library.functions import (
99
BatchCreateRegisteredFunctionJobsDB,
10+
BatchUpdateRegisteredFunctionJobsDB,
1011
FunctionClass,
1112
FunctionClassSpecificData,
1213
FunctionID,
1314
FunctionInputsList,
1415
FunctionJobCollectionID,
1516
FunctionJobDB,
1617
FunctionJobID,
18+
FunctionJobPatchRequest,
1719
FunctionJobStatus,
1820
FunctionOutputs,
1921
FunctionsApiAccessRights,
2022
RegisteredFunctionJobDB,
2123
RegisteredFunctionJobPatch,
2224
RegisteredFunctionJobWithStatusDB,
23-
RegisteredProjectFunctionJobPatchInputList,
24-
RegisteredSolverFunctionJobPatchInputList,
2525
)
2626
from models_library.functions_errors import (
2727
FunctionJobIDNotFoundError,
@@ -130,24 +130,14 @@ async def create_function_jobs( # noqa: PLR0913
130130
return BatchCreateRegisteredFunctionJobsDB(created_items=created_jobs)
131131

132132

133-
async def patch_function_job(
133+
async def patch_function_jobs(
134134
app: web.Application,
135135
connection: AsyncConnection | None = None,
136136
*,
137137
user_id: UserID,
138138
product_name: ProductName,
139-
registered_function_job_patch_inputs: (
140-
RegisteredProjectFunctionJobPatchInputList
141-
| RegisteredSolverFunctionJobPatchInputList
142-
),
143-
) -> list[RegisteredFunctionJobDB]:
144-
145-
# check only a single function class is used
146-
TypeAdapter(
147-
RegisteredProjectFunctionJobPatchInputList
148-
| RegisteredSolverFunctionJobPatchInputList
149-
).validate_python(registered_function_job_patch_inputs)
150-
used_function_class = registered_function_job_patch_inputs[0].patch.function_class
139+
function_job_patch_requests: list[FunctionJobPatchRequest],
140+
) -> BatchUpdateRegisteredFunctionJobsDB:
151141

152142
async with transaction_context(get_asyncpg_engine(app), connection) as transaction:
153143
await check_user_api_access_rights(
@@ -160,41 +150,41 @@ async def patch_function_job(
160150
],
161151
)
162152
updated_jobs = []
163-
for patch_input in registered_function_job_patch_inputs:
153+
for patch_request in function_job_patch_requests:
164154

165155
job = await get_function_job(
166156
app,
167157
connection=transaction,
168158
user_id=user_id,
169159
product_name=product_name,
170-
function_job_id=patch_input.uid,
160+
function_job_id=patch_request.uid,
171161
)
172-
if job.function_class != used_function_class:
162+
if job.function_class != patch_request.patch.function_class:
173163
raise FunctionJobPatchModelIncompatibleError(
174164
function_id=job.function_uuid, product_name=product_name
175165
)
176166

177167
class_specific_data = _update_class_specific_data(
178-
class_specific_data=job.class_specific_data, patch=patch_input.patch
168+
class_specific_data=job.class_specific_data, patch=patch_request.patch
179169
)
180170
update_values = {
181-
"inputs": patch_input.patch.inputs,
182-
"outputs": patch_input.patch.outputs,
171+
"inputs": patch_request.patch.inputs,
172+
"outputs": patch_request.patch.outputs,
183173
"class_specific_data": class_specific_data,
184-
"title": patch_input.patch.title,
185-
"description": patch_input.patch.description,
174+
"title": patch_request.patch.title,
175+
"description": patch_request.patch.description,
186176
"status": "created",
187177
}
188178

189179
result = await transaction.execute(
190180
function_jobs_table.update()
191-
.where(function_jobs_table.c.uuid == f"{patch_input.uid}")
181+
.where(function_jobs_table.c.uuid == f"{patch_request.uid}")
192182
.values(**{k: v for k, v in update_values.items() if v is not None})
193183
.returning(*_FUNCTION_JOBS_TABLE_COLS)
194184
)
195185
updated_jobs.append(RegisteredFunctionJobDB.model_validate(result.one()))
196186

197-
return updated_jobs
187+
return BatchUpdateRegisteredFunctionJobsDB(updated_items=updated_jobs)
198188

199189

200190
async def list_function_jobs_with_status(

services/web/server/src/simcore_service_webserver/functions/_functions_service.py

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from models_library.basic_types import IDStr
55
from models_library.functions import (
66
BatchCreateRegisteredFunctionJobs,
7+
BatchUpdateRegisteredFunctionJobs,
78
Function,
89
FunctionClass,
910
FunctionClassSpecificData,
@@ -20,6 +21,8 @@
2021
FunctionJobDB,
2122
FunctionJobID,
2223
FunctionJobList,
24+
FunctionJobPatchRequest,
25+
FunctionJobPatchRequestList,
2326
FunctionJobStatus,
2427
FunctionOutputs,
2528
FunctionOutputSchema,
@@ -35,11 +38,9 @@
3538
RegisteredFunctionJobWithStatusDB,
3639
RegisteredProjectFunction,
3740
RegisteredProjectFunctionJob,
38-
RegisteredProjectFunctionJobPatchInputList,
3941
RegisteredProjectFunctionJobWithStatus,
4042
RegisteredSolverFunction,
4143
RegisteredSolverFunctionJob,
42-
RegisteredSolverFunctionJobPatchInputList,
4344
RegisteredSolverFunctionJobWithStatus,
4445
)
4546
from models_library.functions_errors import (
@@ -134,19 +135,39 @@ async def patch_registered_function_job(
134135
*,
135136
user_id: UserID,
136137
product_name: ProductName,
137-
registered_function_job_patch_inputs: (
138-
RegisteredProjectFunctionJobPatchInputList
139-
| RegisteredSolverFunctionJobPatchInputList
140-
),
141-
) -> list[RegisteredFunctionJob]:
138+
function_job_patch_request: FunctionJobPatchRequest,
139+
) -> RegisteredFunctionJob:
140+
141+
result = await _function_jobs_repository.patch_function_jobs(
142+
app=app,
143+
user_id=user_id,
144+
product_name=product_name,
145+
function_job_patch_requests=[function_job_patch_request],
146+
)
147+
assert len(result.updated_items) == 1 # nosec
148+
return _decode_functionjob(result.updated_items[0])
149+
142150

143-
result = await _function_jobs_repository.patch_function_job(
151+
async def batch_patch_registered_function_jobs(
152+
app: web.Application,
153+
*,
154+
user_id: UserID,
155+
product_name: ProductName,
156+
function_job_patch_requests: FunctionJobPatchRequestList,
157+
) -> BatchUpdateRegisteredFunctionJobs:
158+
TypeAdapter(FunctionJobPatchRequestList).validate_python(
159+
function_job_patch_requests
160+
)
161+
162+
result = await _function_jobs_repository.patch_function_jobs(
144163
app=app,
145164
user_id=user_id,
146165
product_name=product_name,
147-
registered_function_job_patch_inputs=registered_function_job_patch_inputs,
166+
function_job_patch_requests=function_job_patch_requests,
167+
)
168+
return BatchUpdateRegisteredFunctionJobs(
169+
updated_items=[_decode_functionjob(job) for job in result.updated_items]
148170
)
149-
return [_decode_functionjob(job) for job in result]
150171

151172

152173
async def register_function_job_collection(

0 commit comments

Comments
 (0)