Skip to content

Commit dc39380

Browse files
committed
Add tests to status/output fetching function jobs
1 parent c5bd308 commit dc39380

File tree

9 files changed

+516
-25
lines changed

9 files changed

+516
-25
lines changed

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

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
)
1919
from models_library.functions import (
2020
FunctionJobStatus,
21+
FunctionOutputs,
2122
FunctionUserAccessRights,
2223
FunctionUserApiAccessRights,
2324
)
@@ -319,6 +320,64 @@ async def get_function_job_status(
319320
return TypeAdapter(FunctionJobStatus).validate_python(result)
320321

321322

323+
@log_decorator(_logger, level=logging.DEBUG)
324+
async def get_function_job_outputs(
325+
rabbitmq_rpc_client: RabbitMQRPCClient,
326+
*,
327+
user_id: UserID,
328+
function_job_id: FunctionJobID,
329+
product_name: ProductName,
330+
) -> FunctionOutputs:
331+
result = await rabbitmq_rpc_client.request(
332+
WEBSERVER_RPC_NAMESPACE,
333+
TypeAdapter(RPCMethodName).validate_python("get_function_job_outputs"),
334+
function_job_id=function_job_id,
335+
user_id=user_id,
336+
product_name=product_name,
337+
)
338+
return TypeAdapter(FunctionOutputs).validate_python(result)
339+
340+
341+
@log_decorator(_logger, level=logging.DEBUG)
342+
async def update_function_job_status(
343+
rabbitmq_rpc_client: RabbitMQRPCClient,
344+
*,
345+
user_id: UserID,
346+
product_name: ProductName,
347+
function_job_id: FunctionJobID,
348+
job_status: FunctionJobStatus,
349+
) -> FunctionJobStatus:
350+
result = await rabbitmq_rpc_client.request(
351+
WEBSERVER_RPC_NAMESPACE,
352+
TypeAdapter(RPCMethodName).validate_python("update_function_job_status"),
353+
function_job_id=function_job_id,
354+
job_status=job_status,
355+
user_id=user_id,
356+
product_name=product_name,
357+
)
358+
return TypeAdapter(FunctionJobStatus).validate_python(result)
359+
360+
361+
@log_decorator(_logger, level=logging.DEBUG)
362+
async def update_function_job_outputs(
363+
rabbitmq_rpc_client: RabbitMQRPCClient,
364+
*,
365+
user_id: UserID,
366+
product_name: ProductName,
367+
function_job_id: FunctionJobID,
368+
outputs: FunctionOutputs,
369+
) -> FunctionOutputs:
370+
result = await rabbitmq_rpc_client.request(
371+
WEBSERVER_RPC_NAMESPACE,
372+
TypeAdapter(RPCMethodName).validate_python("update_function_job_outputs"),
373+
function_job_id=function_job_id,
374+
outputs=outputs,
375+
user_id=user_id,
376+
product_name=product_name,
377+
)
378+
return TypeAdapter(FunctionOutputs).validate_python(result)
379+
380+
322381
@log_decorator(_logger, level=logging.DEBUG)
323382
async def delete_function_job(
324383
rabbitmq_rpc_client: RabbitMQRPCClient,

services/api-server/src/simcore_service_api_server/api/routes/function_jobs_routes.py

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,7 @@ async def function_job_status(
199199
user_id=user_id,
200200
director2_api=director2_api,
201201
)
202-
job_status = FunctionJobStatus(status=job_status.state)
203-
204-
if (function.function_class == FunctionClass.SOLVER) and (
202+
elif (function.function_class == FunctionClass.SOLVER) and (
205203
function_job.function_class == FunctionClass.SOLVER
206204
):
207205
job_status = await solvers_jobs.inspect_job(
@@ -211,13 +209,20 @@ async def function_job_status(
211209
user_id=user_id,
212210
director2_api=director2_api,
213211
)
214-
job_status = FunctionJobStatus(status=job_status.state)
215212
else:
216213
raise UnsupportedFunctionFunctionJobClassCombinationError(
217214
function_class=function.function_class,
218215
function_job_class=function_job.function_class,
219216
)
220-
return job_status
217+
218+
new_job_status = FunctionJobStatus(status=job_status.state)
219+
220+
return await wb_api_rpc.update_function_job_status(
221+
function_job_id=function_job.uid,
222+
user_id=user_id,
223+
product_name=product_name,
224+
job_status=new_job_status,
225+
)
221226

222227

223228
async def get_function_from_functionjobid(
@@ -255,7 +260,7 @@ async def get_function_from_functionjobid(
255260
changelog=CHANGE_LOGS["function_job_outputs"],
256261
),
257262
)
258-
async def function_job_outputs(
263+
async def get_function_job_outputs(
259264
function_job_id: FunctionJobID,
260265
webserver_api: Annotated[AuthSession, Depends(get_webserver_session)],
261266
user_id: Annotated[UserID, Depends(get_current_user_id)],
@@ -271,11 +276,18 @@ async def function_job_outputs(
271276
product_name=product_name,
272277
)
273278

279+
old_job_outputs = await wb_api_rpc.get_function_job_outputs(
280+
function_job_id=function_job.uid, user_id=user_id, product_name=product_name
281+
)
282+
283+
if old_job_outputs is not None:
284+
return old_job_outputs
285+
274286
if (
275287
function.function_class == FunctionClass.PROJECT
276288
and function_job.function_class == FunctionClass.PROJECT
277289
):
278-
return dict(
290+
new_outputs = dict(
279291
(
280292
await studies_jobs.get_study_job_outputs(
281293
study_id=function.project_id,
@@ -286,12 +298,11 @@ async def function_job_outputs(
286298
)
287299
).results
288300
)
289-
290-
if (
301+
elif (
291302
function.function_class == FunctionClass.SOLVER
292303
and function_job.function_class == FunctionClass.SOLVER
293304
):
294-
return dict(
305+
new_outputs = dict(
295306
(
296307
await solvers_jobs_read.get_job_outputs(
297308
solver_key=function.solver_key,
@@ -304,4 +315,12 @@ async def function_job_outputs(
304315
)
305316
).results
306317
)
307-
raise UnsupportedFunctionClassError(function_class=function.function_class)
318+
else:
319+
raise UnsupportedFunctionClassError(function_class=function.function_class)
320+
321+
return await wb_api_rpc.update_function_job_outputs(
322+
function_job_id=function_job.uid,
323+
user_id=user_id,
324+
product_name=product_name,
325+
outputs=new_outputs,
326+
)

services/api-server/src/simcore_service_api_server/services_rpc/wb_api_server.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from models_library.api_schemas_webserver.licensed_items import LicensedItemRpcGetPage
2626
from models_library.functions import (
2727
FunctionJobStatus,
28+
FunctionOutputs,
2829
FunctionUserAccessRights,
2930
FunctionUserApiAccessRights,
3031
)
@@ -487,6 +488,52 @@ async def get_function_job_status(
487488
function_job_id=function_job_id,
488489
)
489490

491+
async def get_function_job_outputs(
492+
self,
493+
*,
494+
user_id: UserID,
495+
product_name: ProductName,
496+
function_job_id: FunctionJobID,
497+
) -> FunctionOutputs:
498+
return await functions_rpc_interface.get_function_job_outputs(
499+
self._client,
500+
user_id=user_id,
501+
product_name=product_name,
502+
function_job_id=function_job_id,
503+
)
504+
505+
async def update_function_job_status(
506+
self,
507+
*,
508+
function_job_id: FunctionJobID,
509+
user_id: UserID,
510+
product_name: ProductName,
511+
job_status: FunctionJobStatus,
512+
) -> FunctionJobStatus:
513+
return await functions_rpc_interface.update_function_job_status(
514+
self._client,
515+
function_job_id=function_job_id,
516+
user_id=user_id,
517+
product_name=product_name,
518+
job_status=job_status,
519+
)
520+
521+
async def update_function_job_outputs(
522+
self,
523+
*,
524+
function_job_id: FunctionJobID,
525+
user_id: UserID,
526+
product_name: ProductName,
527+
outputs: FunctionOutputs,
528+
) -> FunctionOutputs:
529+
return await functions_rpc_interface.update_function_job_outputs(
530+
self._client,
531+
function_job_id=function_job_id,
532+
user_id=user_id,
533+
product_name=product_name,
534+
outputs=outputs,
535+
)
536+
490537
async def find_cached_function_jobs(
491538
self,
492539
*,

services/api-server/tests/unit/api_functions/conftest.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,3 +232,24 @@ def _mock(
232232
)
233233

234234
return _mock
235+
236+
237+
@pytest.fixture()
238+
def mock_handler_in_study_jobs_rest_interface(
239+
mock_wb_api_server_rpc: MockerFixture,
240+
) -> Callable[[str, Any, Exception | None], None]:
241+
def _mock(
242+
handler_name: str = "",
243+
return_value: Any = None,
244+
exception: Exception | None = None,
245+
) -> None:
246+
from simcore_service_api_server.api.routes.functions_routes import studies_jobs
247+
248+
mock_wb_api_server_rpc.patch.object(
249+
studies_jobs,
250+
handler_name,
251+
return_value=return_value,
252+
side_effect=exception,
253+
)
254+
255+
return _mock

services/api-server/tests/unit/api_functions/test_api_routers_function_jobs.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# pylint: disable=unused-argument
2+
13
import uuid
24
from collections.abc import Callable
35
from datetime import datetime
@@ -108,12 +110,13 @@ async def test_list_function_jobs(
108110
)
109111

110112

111-
@pytest.mark.parametrize("job_status", ["SUCCESS", "FAILED", "RUNNING"])
113+
@pytest.mark.parametrize("job_status", ["SUCCESS", "FAILED", "STARTED"])
112114
async def test_get_function_job_status(
113115
client: AsyncClient,
114116
mock_handler_in_functions_rpc_interface: Callable[[str, Any], None],
115117
mock_registered_function_job: RegisteredProjectFunctionJob,
116118
mock_registered_project_function: RegisteredProjectFunction,
119+
mock_handler_in_study_jobs_rest_interface: Callable[[str, Any], None],
117120
auth: httpx.BasicAuth,
118121
job_status: str,
119122
) -> None:
@@ -128,16 +131,20 @@ async def test_get_function_job_status(
128131
"get_function_job_status",
129132
FunctionJobStatus(status=job_status),
130133
)
131-
mock_handler_in_functions_rpc_interface(
134+
mock_handler_in_study_jobs_rest_interface(
132135
"inspect_study_job",
133136
JobStatus(
134137
job_id=uuid.uuid4(),
135138
submitted_at=datetime.fromisoformat("2023-01-01T00:00:00"),
136139
started_at=datetime.fromisoformat("2023-01-01T01:00:00"),
137140
stopped_at=datetime.fromisoformat("2023-01-01T02:00:00"),
138-
state=RunningState(job_status),
141+
state=RunningState(value=job_status),
139142
),
140143
)
144+
mock_handler_in_functions_rpc_interface(
145+
"update_function_job_status",
146+
FunctionJobStatus(status=job_status),
147+
)
141148

142149
response = await client.get(
143150
f"{API_VTAG}/function_jobs/{mock_registered_function_job.uid}/status",
@@ -146,3 +153,30 @@ async def test_get_function_job_status(
146153
assert response.status_code == status.HTTP_200_OK
147154
data = response.json()
148155
assert data["status"] == job_status
156+
157+
158+
@pytest.mark.parametrize("job_outputs", [{"X+Y": 42, "X-Y": 10}])
159+
async def test_get_function_job_outputs(
160+
client: AsyncClient,
161+
mock_handler_in_functions_rpc_interface: Callable[[str, Any], None],
162+
mock_registered_function_job: RegisteredProjectFunctionJob,
163+
mock_registered_project_function: RegisteredProjectFunction,
164+
auth: httpx.BasicAuth,
165+
job_outputs: dict[str, Any],
166+
) -> None:
167+
168+
mock_handler_in_functions_rpc_interface(
169+
"get_function_job", mock_registered_function_job
170+
)
171+
mock_handler_in_functions_rpc_interface(
172+
"get_function", mock_registered_project_function
173+
)
174+
mock_handler_in_functions_rpc_interface("get_function_job_outputs", job_outputs)
175+
176+
response = await client.get(
177+
f"{API_VTAG}/function_jobs/{mock_registered_function_job.uid}/outputs",
178+
auth=auth,
179+
)
180+
assert response.status_code == status.HTTP_200_OK
181+
data = response.json()
182+
assert data == job_outputs

0 commit comments

Comments
 (0)