From d7b8ce8bdd7b3b48e372a3fe5606a82ce2e91c59 Mon Sep 17 00:00:00 2001 From: Werner Van Geit Date: Mon, 2 Jun 2025 11:16:27 +0200 Subject: [PATCH 1/8] Make sure function job was successful before returning cache --- .../functions/functions_rpc_interface.py | 8 +-- .../api/routes/functions_routes.py | 16 ++++- .../services_rpc/wb_api_server.py | 6 +- .../functions/_controller/_functions_rpc.py | 6 +- .../functions/_functions_repository.py | 29 ++++---- .../functions/_functions_service.py | 68 ++++++++++-------- .../test_functions_controller_rpc.py | 71 +++++++++++++++++++ 7 files changed, 147 insertions(+), 57 deletions(-) diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/functions/functions_rpc_interface.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/functions/functions_rpc_interface.py index dae54656e02..0ab7e17756a 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/functions/functions_rpc_interface.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/webserver/functions/functions_rpc_interface.py @@ -315,17 +315,17 @@ async def delete_function_job( @log_decorator(_logger, level=logging.DEBUG) -async def find_cached_function_job( +async def find_cached_function_jobs( rabbitmq_rpc_client: RabbitMQRPCClient, *, user_id: UserID, product_name: ProductName, function_id: FunctionID, inputs: FunctionInputs, -) -> RegisteredFunctionJob | None: +) -> list[RegisteredFunctionJob] | None: result = await rabbitmq_rpc_client.request( WEBSERVER_RPC_NAMESPACE, - TypeAdapter(RPCMethodName).validate_python("find_cached_function_job"), + TypeAdapter(RPCMethodName).validate_python("find_cached_function_jobs"), function_id=function_id, inputs=inputs, user_id=user_id, @@ -333,7 +333,7 @@ async def find_cached_function_job( ) if result is None: return None - return TypeAdapter(RegisteredFunctionJob).validate_python(result) + return TypeAdapter(list[RegisteredFunctionJob]).validate_python(result) @log_decorator(_logger, level=logging.DEBUG) diff --git a/services/api-server/src/simcore_service_api_server/api/routes/functions_routes.py b/services/api-server/src/simcore_service_api_server/api/routes/functions_routes.py index a9abf3a8b16..1cd933a1bb2 100644 --- a/services/api-server/src/simcore_service_api_server/api/routes/functions_routes.py +++ b/services/api-server/src/simcore_service_api_server/api/routes/functions_routes.py @@ -26,6 +26,7 @@ UnsupportedFunctionClassError, ) from models_library.products import ProductName +from models_library.projects_state import RunningState from models_library.users import UserID from servicelib.fastapi.dependencies import get_reverse_url_mapper from simcore_service_api_server._service_jobs import JobService @@ -351,6 +352,8 @@ async def run_function( # noqa: PLR0913 job_service: Annotated[JobService, Depends(get_job_service)], ) -> RegisteredFunctionJob: + from .function_jobs_routes import function_job_status + to_run_function = await wb_api_rpc.get_function( function_id=function_id, user_id=user_id, product_name=product_name ) @@ -371,13 +374,22 @@ async def run_function( # noqa: PLR0913 if not is_valid: raise FunctionInputsValidationError(error=validation_str) - if cached_function_job := await wb_api_rpc.find_cached_function_job( + if cached_function_jobs := await wb_api_rpc.find_cached_function_jobs( function_id=to_run_function.uid, inputs=joined_inputs, user_id=user_id, product_name=product_name, ): - return cached_function_job + for cached_function_job in cached_function_jobs: + job_status = await function_job_status( + wb_api_rpc=wb_api_rpc, + director2_api=director2_api, + function_job_id=cached_function_job.uid, + user_id=user_id, + product_name=product_name, + ) + if job_status.status == RunningState.SUCCESS: + return cached_function_job if to_run_function.function_class == FunctionClass.PROJECT: study_job = await studies_jobs.create_study_job( diff --git a/services/api-server/src/simcore_service_api_server/services_rpc/wb_api_server.py b/services/api-server/src/simcore_service_api_server/services_rpc/wb_api_server.py index dd216cb0c34..d2bb3be899e 100644 --- a/services/api-server/src/simcore_service_api_server/services_rpc/wb_api_server.py +++ b/services/api-server/src/simcore_service_api_server/services_rpc/wb_api_server.py @@ -468,15 +468,15 @@ async def get_function_output_schema( function_id=function_id, ) - async def find_cached_function_job( + async def find_cached_function_jobs( self, *, user_id: UserID, product_name: ProductName, function_id: FunctionID, inputs: FunctionInputs, - ) -> RegisteredFunctionJob | None: - return await functions_rpc_interface.find_cached_function_job( + ) -> list[RegisteredFunctionJob] | None: + return await functions_rpc_interface.find_cached_function_jobs( self._client, user_id=user_id, product_name=product_name, diff --git a/services/web/server/src/simcore_service_webserver/functions/_controller/_functions_rpc.py b/services/web/server/src/simcore_service_webserver/functions/_controller/_functions_rpc.py index 5aa59db3232..0add310f33e 100644 --- a/services/web/server/src/simcore_service_webserver/functions/_controller/_functions_rpc.py +++ b/services/web/server/src/simcore_service_webserver/functions/_controller/_functions_rpc.py @@ -303,15 +303,15 @@ async def update_function_description( @router.expose() -async def find_cached_function_job( +async def find_cached_function_jobs( app: web.Application, *, user_id: UserID, product_name: ProductName, function_id: FunctionID, inputs: FunctionInputs, -) -> FunctionJob | None: - return await _functions_service.find_cached_function_job( +) -> list[FunctionJob] | None: + return await _functions_service.find_cached_function_jobs( app=app, user_id=user_id, product_name=product_name, diff --git a/services/web/server/src/simcore_service_webserver/functions/_functions_repository.py b/services/web/server/src/simcore_service_webserver/functions/_functions_repository.py index 935ae29a9c7..a4d2a55496f 100644 --- a/services/web/server/src/simcore_service_webserver/functions/_functions_repository.py +++ b/services/web/server/src/simcore_service_webserver/functions/_functions_repository.py @@ -687,7 +687,7 @@ async def delete_function_job( ) -async def find_cached_function_job( +async def find_cached_function_jobs( app: web.Application, connection: AsyncConnection | None = None, *, @@ -695,7 +695,7 @@ async def find_cached_function_job( function_id: FunctionID, product_name: ProductName, inputs: FunctionInputs, -) -> RegisteredFunctionJobDB | None: +) -> list[RegisteredFunctionJobDB] | None: async with transaction_context(get_asyncpg_engine(app), connection) as conn: result = await conn.stream( @@ -704,19 +704,13 @@ async def find_cached_function_job( cast(function_jobs_table.c.inputs, Text) == json.dumps(inputs), ), ) - rows = await result.all() - if rows is None or len(rows) == 0: - return None - - assert len(rows) == 1, ( - "More than one function job found with the same function id and inputs." - f" Function id: {function_id}, Inputs: {inputs}" - ) # nosec - - row = rows[0] + if rows is None or len(rows) == 0: + return None + jobs = [] + for row in rows: job = RegisteredFunctionJobDB.model_validate(dict(row)) try: await check_user_permissions( @@ -729,13 +723,14 @@ async def find_cached_function_job( permissions=["read"], ) except FunctionJobReadAccessDeniedError: - # If the user does not have read access, return None - return None + continue - if job.inputs == inputs: - return job + jobs.append(job) - return None + if len(jobs) > 0: + return jobs + + return None async def get_function_job_collection( diff --git a/services/web/server/src/simcore_service_webserver/functions/_functions_service.py b/services/web/server/src/simcore_service_webserver/functions/_functions_service.py index ad09c4aa64d..4a30613b180 100644 --- a/services/web/server/src/simcore_service_webserver/functions/_functions_service.py +++ b/services/web/server/src/simcore_service_webserver/functions/_functions_service.py @@ -340,48 +340,60 @@ async def update_function_description( @router.expose() -async def find_cached_function_job( +async def find_cached_function_jobs( app: web.Application, *, user_id: UserID, product_name: ProductName, function_id: FunctionID, inputs: FunctionInputs, -) -> FunctionJob | None: - returned_function_job = await _functions_repository.find_cached_function_job( +) -> list[FunctionJob] | None: + returned_function_jobs = await _functions_repository.find_cached_function_jobs( app=app, user_id=user_id, product_name=product_name, function_id=function_id, inputs=inputs, ) - if returned_function_job is None: + if returned_function_jobs is None or len(returned_function_jobs) == 0: return None - if returned_function_job.function_class == FunctionClass.PROJECT: - return RegisteredProjectFunctionJob( - uid=returned_function_job.uuid, - title=returned_function_job.title, - description=returned_function_job.description, - function_uid=returned_function_job.function_uuid, - inputs=returned_function_job.inputs, - outputs=None, - project_job_id=returned_function_job.class_specific_data["project_job_id"], - ) - if returned_function_job.function_class == FunctionClass.SOLVER: - return RegisteredSolverFunctionJob( - uid=returned_function_job.uuid, - title=returned_function_job.title, - description=returned_function_job.description, - function_uid=returned_function_job.function_uuid, - inputs=returned_function_job.inputs, - outputs=None, - solver_job_id=returned_function_job.class_specific_data["solver_job_id"], - ) - - raise UnsupportedFunctionJobClassError( - function_job_class=returned_function_job.function_class - ) + to_return_function_jobs = [] + for returned_function_job in returned_function_jobs: + if returned_function_job.function_class == FunctionClass.PROJECT: + to_return_function_jobs.append( + RegisteredProjectFunctionJob( + uid=returned_function_job.uuid, + title=returned_function_job.title, + description=returned_function_job.description, + function_uid=returned_function_job.function_uuid, + inputs=returned_function_job.inputs, + outputs=None, + project_job_id=returned_function_job.class_specific_data[ + "project_job_id" + ], + ) + ) + elif returned_function_job.function_class == FunctionClass.SOLVER: + to_return_function_jobs.append( + RegisteredSolverFunctionJob( + uid=returned_function_job.uuid, + title=returned_function_job.title, + description=returned_function_job.description, + function_uid=returned_function_job.function_uuid, + inputs=returned_function_job.inputs, + outputs=None, + solver_job_id=returned_function_job.class_specific_data[ + "solver_job_id" + ], + ) + ) + else: + raise UnsupportedFunctionJobClassError( + function_job_class=returned_function_job.function_class + ) + + return to_return_function_jobs @router.expose(reraise_if_error_type=(FunctionIDNotFoundError,)) diff --git a/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_functions_controller_rpc.py b/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_functions_controller_rpc.py index 2fbc79367cc..86c32d56dde 100644 --- a/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_functions_controller_rpc.py +++ b/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_functions_controller_rpc.py @@ -1125,3 +1125,74 @@ async def test_list_function_job_collections_filtered_function_id( assert collections[1].uid in [ collection.uid for collection in registered_collections ] + + +@pytest.mark.parametrize( + "user_role", + [UserRole.USER], +) +async def test_find_cached_function_jobs( + client: TestClient, + rpc_client: RabbitMQRPCClient, + logged_user: UserInfoDict, + other_logged_user: UserInfoDict, + osparc_product_name: ProductName, + mock_function: ProjectFunction, + clean_functions: None, +): + + # Register the function first + registered_function = await functions_rpc.register_function( + rabbitmq_rpc_client=rpc_client, + function=mock_function, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + + registered_function_jobs = [] + for value in range(5): + function_job = ProjectFunctionJob( + function_uid=registered_function.uid, + title="Test Function Job", + description="A test function job", + project_job_id=uuid4(), + inputs={"input1": value if value < 4 else 1}, + outputs={"output1": "result1"}, + ) + + # Register the function job + registered_job = await functions_rpc.register_function_job( + rabbitmq_rpc_client=rpc_client, + function_job=function_job, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + registered_function_jobs.append(registered_job) + + # Find cached function jobs + cached_jobs = await functions_rpc.find_cached_function_jobs( + rabbitmq_rpc_client=rpc_client, + function_id=registered_function.uid, + inputs={"input1": 1}, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + + # Assert the cached jobs contain the registered job + assert cached_jobs is not None + assert len(cached_jobs) == 2 + assert {job.uid for job in cached_jobs} == { + registered_function_jobs[1].uid, + registered_function_jobs[4].uid, + } + + cached_jobs = await functions_rpc.find_cached_function_jobs( + rabbitmq_rpc_client=rpc_client, + function_id=registered_function.uid, + inputs={"input1": 1}, + user_id=other_logged_user["id"], + product_name=osparc_product_name, + ) + + # Assert the cached jobs does not contain the registered job for the other user + assert cached_jobs is None From a72507ae040460a186a4f5528710d02556c3e0d3 Mon Sep 17 00:00:00 2001 From: Werner Van Geit Date: Mon, 2 Jun 2025 13:25:37 +0200 Subject: [PATCH 2/8] Fix return type error --- .../functions/_controller/_functions_rpc.py | 2 +- .../simcore_service_webserver/functions/_functions_service.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/functions/_controller/_functions_rpc.py b/services/web/server/src/simcore_service_webserver/functions/_controller/_functions_rpc.py index 0add310f33e..b678df299c3 100644 --- a/services/web/server/src/simcore_service_webserver/functions/_controller/_functions_rpc.py +++ b/services/web/server/src/simcore_service_webserver/functions/_controller/_functions_rpc.py @@ -310,7 +310,7 @@ async def find_cached_function_jobs( product_name: ProductName, function_id: FunctionID, inputs: FunctionInputs, -) -> list[FunctionJob] | None: +) -> list[RegisteredFunctionJob] | None: return await _functions_service.find_cached_function_jobs( app=app, user_id=user_id, diff --git a/services/web/server/src/simcore_service_webserver/functions/_functions_service.py b/services/web/server/src/simcore_service_webserver/functions/_functions_service.py index 4a30613b180..7e23720292f 100644 --- a/services/web/server/src/simcore_service_webserver/functions/_functions_service.py +++ b/services/web/server/src/simcore_service_webserver/functions/_functions_service.py @@ -347,7 +347,7 @@ async def find_cached_function_jobs( product_name: ProductName, function_id: FunctionID, inputs: FunctionInputs, -) -> list[FunctionJob] | None: +) -> list[RegisteredFunctionJob] | None: returned_function_jobs = await _functions_repository.find_cached_function_jobs( app=app, user_id=user_id, From e8da50cc6ddc906c866b045e11fba57e70c27476 Mon Sep 17 00:00:00 2001 From: Werner Van Geit Date: Mon, 2 Jun 2025 14:57:21 +0200 Subject: [PATCH 3/8] Fix mypy bug --- .../simcore_service_webserver/functions/_functions_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/server/src/simcore_service_webserver/functions/_functions_service.py b/services/web/server/src/simcore_service_webserver/functions/_functions_service.py index 7e23720292f..e6d261d66c8 100644 --- a/services/web/server/src/simcore_service_webserver/functions/_functions_service.py +++ b/services/web/server/src/simcore_service_webserver/functions/_functions_service.py @@ -358,7 +358,7 @@ async def find_cached_function_jobs( if returned_function_jobs is None or len(returned_function_jobs) == 0: return None - to_return_function_jobs = [] + to_return_function_jobs: list[RegisteredFunctionJob] = [] for returned_function_job in returned_function_jobs: if returned_function_job.function_class == FunctionClass.PROJECT: to_return_function_jobs.append( From 6314ffe0c13926fe576f868825648030ad74e91a Mon Sep 17 00:00:00 2001 From: Werner Van Geit Date: Tue, 3 Jun 2025 14:44:18 +0200 Subject: [PATCH 4/8] Return the created_at data of registered function api objects --- .../src/models_library/functions.py | 7 + .../tests/unit/api_functions/conftest.py | 21 +- .../test_api_routers_functions.py | 13 +- .../functions/_functions_service.py | 9 + .../with_dbs/04/functions_rpc/conftest.py | 58 +- ...function_job_collections_controller_rpc.py | 347 +++++++++ .../test_function_jobs_controller_rpc.py | 355 +++++++++ .../test_functions_controller_rest.py | 2 + .../test_functions_controller_rpc.py | 727 +----------------- 9 files changed, 814 insertions(+), 725 deletions(-) create mode 100644 services/web/server/tests/unit/with_dbs/04/functions_rpc/test_function_job_collections_controller_rpc.py create mode 100644 services/web/server/tests/unit/with_dbs/04/functions_rpc/test_function_jobs_controller_rpc.py diff --git a/packages/models-library/src/models_library/functions.py b/packages/models-library/src/models_library/functions.py index 3640084a499..970ff94f9d1 100644 --- a/packages/models-library/src/models_library/functions.py +++ b/packages/models-library/src/models_library/functions.py @@ -1,3 +1,4 @@ +import datetime from collections.abc import Mapping from enum import Enum from typing import Annotated, Any, Literal, TypeAlias @@ -93,6 +94,7 @@ class FunctionBase(BaseModel): class RegisteredFunctionBase(FunctionBase): uid: FunctionID + created_at: datetime.datetime class ProjectFunction(FunctionBase): @@ -149,6 +151,7 @@ class FunctionJobBase(BaseModel): class RegisteredFunctionJobBase(FunctionJobBase): uid: FunctionJobID + created_at: datetime.datetime class ProjectFunctionJob(FunctionJobBase): @@ -204,6 +207,7 @@ class FunctionJobCollection(BaseModel): class RegisteredFunctionJobCollection(FunctionJobCollection): uid: FunctionJobCollectionID + created_at: datetime.datetime class FunctionJobCollectionStatus(BaseModel): @@ -222,6 +226,7 @@ class FunctionJobDB(BaseModel): class RegisteredFunctionJobDB(FunctionJobDB): uuid: FunctionJobID + created: datetime.datetime class FunctionDB(BaseModel): @@ -236,6 +241,7 @@ class FunctionDB(BaseModel): class RegisteredFunctionDB(FunctionDB): uuid: FunctionID + created: datetime.datetime class FunctionJobCollectionDB(BaseModel): @@ -245,6 +251,7 @@ class FunctionJobCollectionDB(BaseModel): class RegisteredFunctionJobCollectionDB(FunctionJobCollectionDB): uuid: FunctionJobCollectionID + created: datetime.datetime class FunctionIDString(ConstrainedStr): diff --git a/services/api-server/tests/unit/api_functions/conftest.py b/services/api-server/tests/unit/api_functions/conftest.py index e71a2a22774..891fdc533f4 100644 --- a/services/api-server/tests/unit/api_functions/conftest.py +++ b/services/api-server/tests/unit/api_functions/conftest.py @@ -5,6 +5,7 @@ # pylint: disable=no-self-use # pylint: disable=cyclic-import +import datetime from collections.abc import Callable from typing import Any from uuid import uuid4 @@ -119,7 +120,13 @@ def mock_function( @pytest.fixture def mock_registered_function(mock_function: Function) -> RegisteredFunction: - return RegisteredProjectFunction(**{**mock_function.dict(), "uid": str(uuid4())}) + return RegisteredProjectFunction( + **{ + **mock_function.dict(), + "uid": str(uuid4()), + "created_at": datetime.datetime.now(datetime.UTC), + } + ) @pytest.fixture @@ -141,7 +148,11 @@ def mock_registered_function_job( mock_function_job: FunctionJob, ) -> RegisteredFunctionJob: return RegisteredProjectFunctionJob( - **{**mock_function_job.dict(), "uid": str(uuid4())} + **{ + **mock_function_job.dict(), + "uid": str(uuid4()), + "created_at": datetime.datetime.now(datetime.UTC), + } ) @@ -165,7 +176,11 @@ def mock_registered_function_job_collection( mock_function_job_collection: FunctionJobCollection, ) -> RegisteredFunctionJobCollection: return RegisteredFunctionJobCollection( - **{**mock_function_job_collection.model_dump(), "uid": str(uuid4())} + **{ + **mock_function_job_collection.model_dump(), + "uid": str(uuid4()), + "created_at": datetime.datetime.now(datetime.UTC), + } ) diff --git a/services/api-server/tests/unit/api_functions/test_api_routers_functions.py b/services/api-server/tests/unit/api_functions/test_api_routers_functions.py index 743a83c5cc2..dcac6487618 100644 --- a/services/api-server/tests/unit/api_functions/test_api_routers_functions.py +++ b/services/api-server/tests/unit/api_functions/test_api_routers_functions.py @@ -1,6 +1,7 @@ # pylint: disable=unused-argument # pylint: disable=redefined-outer-name +import datetime from collections.abc import Callable from typing import Any from uuid import uuid4 @@ -27,12 +28,11 @@ async def test_register_function( mock_handler_in_functions_rpc_interface: Callable[[str, Any], None], mock_function: ProjectFunction, auth: httpx.BasicAuth, + mock_registered_function: RegisteredProjectFunction, ) -> None: - registered_function = RegisteredProjectFunction( - **{**mock_function.model_dump(), "uid": str(uuid4())} + mock_handler_in_functions_rpc_interface( + "register_function", mock_registered_function ) - - mock_handler_in_functions_rpc_interface("register_function", registered_function) response = await client.post( f"{API_VTAG}/functions", json=mock_function.model_dump(mode="json"), auth=auth ) @@ -40,7 +40,7 @@ async def test_register_function( data = response.json() returned_function = RegisteredProjectFunction.model_validate(data) assert returned_function.uid is not None - assert returned_function == registered_function + assert returned_function == mock_registered_function async def test_register_function_invalid( @@ -391,6 +391,7 @@ async def test_register_function_job_collection( { **mock_function_job_collection.model_dump(), "uid": str(uuid4()), + "created_at": datetime.datetime.now(datetime.UTC), } ) ) @@ -425,6 +426,7 @@ async def test_get_function_job_collection( "title": "Test Collection", "description": "A test function job collection", "job_ids": [str(uuid4()), str(uuid4())], + "created_at": datetime.datetime.now(datetime.UTC), } ) ) @@ -456,6 +458,7 @@ async def test_list_function_job_collections( "title": "Test Collection", "description": "A test function job collection", "job_ids": [str(uuid4()), str(uuid4())], + "created_at": datetime.datetime.now(datetime.UTC), } ) ) diff --git a/services/web/server/src/simcore_service_webserver/functions/_functions_service.py b/services/web/server/src/simcore_service_webserver/functions/_functions_service.py index e6d261d66c8..fd29f603d7c 100644 --- a/services/web/server/src/simcore_service_webserver/functions/_functions_service.py +++ b/services/web/server/src/simcore_service_webserver/functions/_functions_service.py @@ -112,6 +112,7 @@ async def register_function_job_collection( title=registered_function_job_collection.title, description=registered_function_job_collection.description, job_ids=registered_job_ids, + created_at=registered_function_job_collection.created, ) @@ -174,6 +175,7 @@ async def get_function_job_collection( title=returned_function_job_collection.title, description=returned_function_job_collection.description, job_ids=returned_job_ids, + created_at=returned_function_job_collection.created, ) @@ -248,6 +250,7 @@ async def list_function_job_collections( title=function_job_collection.title, description=function_job_collection.description, job_ids=job_ids, + created_at=function_job_collection.created, ) for function_job_collection, job_ids in returned_function_job_collections ], page @@ -372,6 +375,7 @@ async def find_cached_function_jobs( project_job_id=returned_function_job.class_specific_data[ "project_job_id" ], + created_at=returned_function_job.created, ) ) elif returned_function_job.function_class == FunctionClass.SOLVER: @@ -386,6 +390,7 @@ async def find_cached_function_jobs( solver_job_id=returned_function_job.class_specific_data[ "solver_job_id" ], + created_at=returned_function_job.created, ) ) else: @@ -442,6 +447,7 @@ def _decode_function( output_schema=function.output_schema, project_id=function.class_specific_data["project_id"], default_inputs=function.default_inputs, + created_at=function.created, ) if function.function_class == FunctionClass.SOLVER: @@ -454,6 +460,7 @@ def _decode_function( solver_key=function.class_specific_data["solver_key"], solver_version=function.class_specific_data["solver_version"], default_inputs=function.default_inputs, + created_at=function.created, ) raise UnsupportedFunctionClassError(function_class=function.function_class) @@ -530,6 +537,7 @@ def _decode_functionjob( inputs=functionjob_db.inputs, outputs=functionjob_db.outputs, project_job_id=functionjob_db.class_specific_data["project_job_id"], + created_at=functionjob_db.created, ) if functionjob_db.function_class == FunctionClass.SOLVER: @@ -541,6 +549,7 @@ def _decode_functionjob( inputs=functionjob_db.inputs, outputs=functionjob_db.outputs, solver_job_id=functionjob_db.class_specific_data["solver_job_id"], + created_at=functionjob_db.created, ) raise UnsupportedFunctionJobClassError( diff --git a/services/web/server/tests/unit/with_dbs/04/functions_rpc/conftest.py b/services/web/server/tests/unit/with_dbs/04/functions_rpc/conftest.py index d6859c089b4..98dfa687c23 100644 --- a/services/web/server/tests/unit/with_dbs/04/functions_rpc/conftest.py +++ b/services/web/server/tests/unit/with_dbs/04/functions_rpc/conftest.py @@ -3,10 +3,17 @@ # pylint:disable=redefined-outer-name -from collections.abc import AsyncIterator +from collections.abc import AsyncIterator, Awaitable, Callable +from uuid import uuid4 import pytest from aiohttp.test_utils import TestClient +from models_library.api_schemas_webserver.functions import ( + Function, + JSONFunctionInputSchema, + JSONFunctionOutputSchema, + ProjectFunction, +) from models_library.products import ProductName from pytest_simcore.helpers.monkeypatch_envs import setenvs_from_dict from pytest_simcore.helpers.typing_env import EnvVarsDict @@ -15,21 +22,64 @@ from servicelib.rabbitmq.rpc_interfaces.webserver.functions import ( functions_rpc_interface as functions_rpc, ) +from settings_library.rabbit import RabbitSettings +from simcore_service_webserver.application_settings import ApplicationSettings @pytest.fixture def app_environment( + rabbit_service: RabbitSettings, app_environment: EnvVarsDict, monkeypatch: pytest.MonkeyPatch, -): - return setenvs_from_dict( +) -> EnvVarsDict: + new_envs = setenvs_from_dict( monkeypatch, { - **app_environment, # WARNING: AFTER env_devel_dict because HOST are set to 127.0.0.1 in here + **app_environment, + "RABBIT_HOST": rabbit_service.RABBIT_HOST, + "RABBIT_PORT": f"{rabbit_service.RABBIT_PORT}", + "RABBIT_USER": rabbit_service.RABBIT_USER, + "RABBIT_SECURE": f"{rabbit_service.RABBIT_SECURE}", + "RABBIT_PASSWORD": rabbit_service.RABBIT_PASSWORD.get_secret_value(), + "WEBSERVER_DEV_FEATURES_ENABLED": "1", "WEBSERVER_FUNCTIONS": "1", }, ) + settings = ApplicationSettings.create_from_envs() + assert settings.WEBSERVER_RABBITMQ + + return new_envs + + +@pytest.fixture +async def rpc_client( + rabbitmq_rpc_client: Callable[[str], Awaitable[RabbitMQRPCClient]], +) -> RabbitMQRPCClient: + return await rabbitmq_rpc_client("client") + + +@pytest.fixture +def mock_function() -> Function: + return ProjectFunction( + title="Test Function", + description="A test function", + input_schema=JSONFunctionInputSchema( + schema_content={ + "type": "object", + "properties": {"input1": {"type": "string"}}, + } + ), + output_schema=JSONFunctionOutputSchema( + schema_content={ + "type": "object", + "properties": {"output1": {"type": "string"}}, + } + ), + project_id=uuid4(), + default_inputs=None, + ) + @pytest.fixture async def other_logged_user( diff --git a/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_function_job_collections_controller_rpc.py b/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_function_job_collections_controller_rpc.py new file mode 100644 index 00000000000..66935054b71 --- /dev/null +++ b/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_function_job_collections_controller_rpc.py @@ -0,0 +1,347 @@ +# pylint: disable=redefined-outer-name +# pylint: disable=unused-argument + +import datetime +from uuid import uuid4 + +import pytest +from aiohttp.test_utils import TestClient +from common_library.users_enums import UserRole +from models_library.api_schemas_webserver.functions import ( + FunctionIDString, + FunctionJobCollection, + ProjectFunction, + ProjectFunctionJob, +) +from models_library.functions import FunctionJobCollectionsListFilters + +# import simcore_service_webserver.functions._functions_controller_rpc as functions_rpc +from models_library.functions_errors import ( + FunctionJobCollectionReadAccessDeniedError, + FunctionJobIDNotFoundError, +) +from models_library.products import ProductName +from pytest_simcore.helpers.webserver_login import UserInfoDict +from servicelib.rabbitmq import RabbitMQRPCClient +from servicelib.rabbitmq.rpc_interfaces.webserver.functions import ( + functions_rpc_interface as functions_rpc, +) + +pytest_simcore_core_services_selection = ["rabbit"] + + +@pytest.mark.parametrize( + "user_role", + [UserRole.USER], +) +async def test_function_job_collection( + client: TestClient, + mock_function: ProjectFunction, + rpc_client: RabbitMQRPCClient, + logged_user: UserInfoDict, + other_logged_user: UserInfoDict, + osparc_product_name: ProductName, +): + # Register the function first + registered_function = await functions_rpc.register_function( + rabbitmq_rpc_client=rpc_client, + function=mock_function, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + assert registered_function.uid is not None + + registered_function_job = ProjectFunctionJob( + function_uid=registered_function.uid, + title="Test Function Job", + description="A test function job", + project_job_id=uuid4(), + inputs={"input1": "value1"}, + outputs={"output1": "result1"}, + ) + # Register the function job + function_job_ids = [] + for _ in range(3): + registered_function_job = ProjectFunctionJob( + function_uid=registered_function.uid, + title="Test Function Job", + description="A test function job", + project_job_id=uuid4(), + inputs={"input1": "value1"}, + outputs={"output1": "result1"}, + ) + # Register the function job + registered_job = await functions_rpc.register_function_job( + rabbitmq_rpc_client=rpc_client, + function_job=registered_function_job, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + assert registered_job.uid is not None + function_job_ids.append(registered_job.uid) + + function_job_collection = FunctionJobCollection( + title="Test Function Job Collection", + description="A test function job collection", + job_ids=function_job_ids, + ) + + # Register the function job collection + registered_collection = await functions_rpc.register_function_job_collection( + rabbitmq_rpc_client=rpc_client, + function_job_collection=function_job_collection, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + assert registered_collection.uid is not None + assert registered_collection.created_at - datetime.datetime.now( + datetime.UTC + ) < datetime.timedelta(seconds=60) + + # Get the function job collection + retrieved_collection = await functions_rpc.get_function_job_collection( + rabbitmq_rpc_client=rpc_client, + function_job_collection_id=registered_collection.uid, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + assert retrieved_collection.uid == registered_collection.uid + assert registered_collection.job_ids == function_job_ids + + # Test denied access for another user + with pytest.raises(FunctionJobCollectionReadAccessDeniedError): + await functions_rpc.get_function_job_collection( + rabbitmq_rpc_client=rpc_client, + function_job_collection_id=registered_collection.uid, + user_id=other_logged_user["id"], + product_name=osparc_product_name, + ) + + # Test denied access for another product + with pytest.raises(FunctionJobCollectionReadAccessDeniedError): + await functions_rpc.get_function_job_collection( + rabbitmq_rpc_client=rpc_client, + function_job_collection_id=registered_collection.uid, + user_id=other_logged_user["id"], + product_name="this_is_not_osparc", + ) + + # Attempt to delete the function job collection by another user + with pytest.raises(FunctionJobCollectionReadAccessDeniedError): + await functions_rpc.delete_function_job_collection( + rabbitmq_rpc_client=rpc_client, + function_job_collection_id=registered_collection.uid, + user_id=other_logged_user["id"], + product_name=osparc_product_name, + ) + + await functions_rpc.delete_function_job_collection( + rabbitmq_rpc_client=rpc_client, + function_job_collection_id=registered_collection.uid, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + # Attempt to retrieve the deleted collection + with pytest.raises(FunctionJobIDNotFoundError): + await functions_rpc.get_function_job( + rabbitmq_rpc_client=rpc_client, + function_job_id=registered_collection.uid, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + + +@pytest.mark.parametrize( + "user_role", + [UserRole.USER], +) +async def test_list_function_job_collections( + client: TestClient, + mock_function: ProjectFunction, + rpc_client: RabbitMQRPCClient, + clean_functions: None, + clean_function_job_collections: None, + logged_user: UserInfoDict, + osparc_product_name: ProductName, +): + # List function job collections when none are registered + collections, page_meta = await functions_rpc.list_function_job_collections( + rabbitmq_rpc_client=rpc_client, + pagination_limit=10, + pagination_offset=0, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + + # Assert the list is empty + assert page_meta.count == 0 + assert page_meta.total == 0 + assert page_meta.offset == 0 + assert len(collections) == 0 + + # Register the function first + registered_function = await functions_rpc.register_function( + rabbitmq_rpc_client=rpc_client, + function=mock_function, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + assert registered_function.uid is not None + + # Create a function job collection + function_job_ids = [] + for _ in range(3): + registered_function_job = ProjectFunctionJob( + function_uid=registered_function.uid, + title="Test Function Job", + description="A test function job", + project_job_id=uuid4(), + inputs={"input1": "value1"}, + outputs={"output1": "result1"}, + ) + # Register the function job + registered_job = await functions_rpc.register_function_job( + rabbitmq_rpc_client=rpc_client, + function_job=registered_function_job, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + assert registered_job.uid is not None + function_job_ids.append(registered_job.uid) + + function_job_collection = FunctionJobCollection( + title="Test Function Job Collection", + description="A test function job collection", + job_ids=function_job_ids, + ) + + # Register the function job collection + registered_collections = [ + await functions_rpc.register_function_job_collection( + rabbitmq_rpc_client=rpc_client, + function_job_collection=function_job_collection, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + for _ in range(3) + ] + assert all( + registered_collection.uid is not None + for registered_collection in registered_collections + ) + + # List function job collections + collections, page_params = await functions_rpc.list_function_job_collections( + rabbitmq_rpc_client=rpc_client, + pagination_limit=2, + pagination_offset=1, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + + # Assert the list contains the registered collection + assert page_params.count == 2 + assert page_params.total == 3 + assert page_params.offset == 1 + assert len(collections) == 2 + assert collections[0].uid in [ + collection.uid for collection in registered_collections + ] + assert collections[1].uid in [ + collection.uid for collection in registered_collections + ] + + +@pytest.mark.parametrize( + "user_role", + [UserRole.USER], +) +async def test_list_function_job_collections_filtered_function_id( + client: TestClient, + rpc_client: RabbitMQRPCClient, + mock_function: ProjectFunction, + clean_functions: None, + clean_function_job_collections: None, + logged_user: UserInfoDict, + osparc_product_name: ProductName, +): + # Register the function first + registered_function = await functions_rpc.register_function( + rabbitmq_rpc_client=rpc_client, + function=mock_function, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + other_registered_function = await functions_rpc.register_function( + rabbitmq_rpc_client=rpc_client, + function=mock_function, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + + registered_collections = [] + for coll_i in range(5): + if coll_i < 3: + function_id = registered_function.uid + else: + function_id = other_registered_function.uid + # Create a function job collection + function_job_ids = [] + for _ in range(3): + registered_function_job = ProjectFunctionJob( + function_uid=function_id, + title="Test Function Job", + description="A test function job", + project_job_id=uuid4(), + inputs={"input1": "value1"}, + outputs={"output1": "result1"}, + ) + # Register the function job + registered_job = await functions_rpc.register_function_job( + rabbitmq_rpc_client=rpc_client, + function_job=registered_function_job, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + assert registered_job.uid is not None + function_job_ids.append(registered_job.uid) + + function_job_collection = FunctionJobCollection( + title="Test Function Job Collection", + description="A test function job collection", + job_ids=function_job_ids, + ) + + # Register the function job collection + registered_collection = await functions_rpc.register_function_job_collection( + rabbitmq_rpc_client=rpc_client, + function_job_collection=function_job_collection, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + registered_collections.append(registered_collection) + + # List function job collections with a specific function ID + collections, page_meta = await functions_rpc.list_function_job_collections( + rabbitmq_rpc_client=rpc_client, + pagination_limit=10, + pagination_offset=1, + filters=FunctionJobCollectionsListFilters( + has_function_id=FunctionIDString(registered_function.uid) + ), + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + + # Assert the list contains the registered collection + assert page_meta.count == 2 + assert page_meta.total == 3 + assert page_meta.offset == 1 + + assert len(collections) == 2 + assert collections[0].uid in [ + collection.uid for collection in registered_collections + ] + assert collections[1].uid in [ + collection.uid for collection in registered_collections + ] diff --git a/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_function_jobs_controller_rpc.py b/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_function_jobs_controller_rpc.py new file mode 100644 index 00000000000..6c9517077a1 --- /dev/null +++ b/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_function_jobs_controller_rpc.py @@ -0,0 +1,355 @@ +# pylint: disable=redefined-outer-name +# pylint: disable=unused-argument + +import datetime +from uuid import uuid4 + +import pytest +from aiohttp.test_utils import TestClient +from common_library.users_enums import UserRole +from models_library.api_schemas_webserver.functions import ( + ProjectFunction, + ProjectFunctionJob, +) + +# import simcore_service_webserver.functions._functions_controller_rpc as functions_rpc +from models_library.functions_errors import ( + FunctionJobIDNotFoundError, + FunctionJobReadAccessDeniedError, +) +from models_library.products import ProductName +from pytest_simcore.helpers.webserver_login import UserInfoDict +from servicelib.rabbitmq import RabbitMQRPCClient +from servicelib.rabbitmq.rpc_interfaces.webserver.functions import ( + functions_rpc_interface as functions_rpc, +) + +pytest_simcore_core_services_selection = ["rabbit"] + + +@pytest.mark.parametrize( + "user_role", + [UserRole.USER], +) +async def test_register_get_delete_function_job( + client: TestClient, + rpc_client: RabbitMQRPCClient, + mock_function: ProjectFunction, + logged_user: UserInfoDict, + other_logged_user: UserInfoDict, + osparc_product_name: ProductName, +): + # Register the function first + registered_function = await functions_rpc.register_function( + rabbitmq_rpc_client=rpc_client, + function=mock_function, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + assert registered_function.uid is not None + + function_job = ProjectFunctionJob( + function_uid=registered_function.uid, + title="Test Function Job", + description="A test function job", + project_job_id=uuid4(), + inputs={"input1": "value1"}, + outputs={"output1": "result1"}, + ) + + # Register the function job + registered_job = await functions_rpc.register_function_job( + rabbitmq_rpc_client=rpc_client, + function_job=function_job, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + + # Assert the registered job matches the input job + assert registered_job.function_uid == function_job.function_uid + assert registered_job.inputs == function_job.inputs + assert registered_job.outputs == function_job.outputs + assert registered_job.created_at - datetime.datetime.now( + datetime.UTC + ) < datetime.timedelta(seconds=60) + + # Retrieve the function job using its ID + retrieved_job = await functions_rpc.get_function_job( + rabbitmq_rpc_client=rpc_client, + function_job_id=registered_job.uid, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + + # Assert the retrieved job matches the registered job + assert retrieved_job.function_uid == registered_job.function_uid + assert retrieved_job.inputs == registered_job.inputs + assert retrieved_job.outputs == registered_job.outputs + + # Test denied access for another user + with pytest.raises(FunctionJobReadAccessDeniedError): + await functions_rpc.get_function_job( + rabbitmq_rpc_client=rpc_client, + function_job_id=registered_job.uid, + user_id=other_logged_user["id"], + product_name=osparc_product_name, + ) + + # Test denied access for anothe product + with pytest.raises(FunctionJobReadAccessDeniedError): + await functions_rpc.get_function_job( + rabbitmq_rpc_client=rpc_client, + function_job_id=registered_job.uid, + user_id=other_logged_user["id"], + product_name="this_is_not_osparc", + ) + + with pytest.raises(FunctionJobReadAccessDeniedError): + # Attempt to delete the function job by another user + await functions_rpc.delete_function_job( + rabbitmq_rpc_client=rpc_client, + function_job_id=registered_job.uid, + user_id=other_logged_user["id"], + product_name=osparc_product_name, + ) + + # Delete the function job using its ID + await functions_rpc.delete_function_job( + rabbitmq_rpc_client=rpc_client, + function_job_id=registered_job.uid, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + + # Attempt to retrieve the deleted job + with pytest.raises(FunctionJobIDNotFoundError): + await functions_rpc.get_function_job( + rabbitmq_rpc_client=rpc_client, + function_job_id=registered_job.uid, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + + +@pytest.mark.parametrize( + "user_role", + [UserRole.USER], +) +async def test_get_function_job_not_found( + client: TestClient, + rpc_client: RabbitMQRPCClient, + logged_user: UserInfoDict, + osparc_product_name: ProductName, + clean_functions: None, +): + # Attempt to retrieve a function job that does not exist + with pytest.raises(FunctionJobIDNotFoundError): + await functions_rpc.get_function_job( + rabbitmq_rpc_client=rpc_client, + function_job_id=uuid4(), + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + + +@pytest.mark.parametrize( + "user_role", + [UserRole.USER], +) +async def test_list_function_jobs( + client: TestClient, + rpc_client: RabbitMQRPCClient, + mock_function: ProjectFunction, + logged_user: UserInfoDict, + osparc_product_name: ProductName, +): + # Register the function first + registered_function = await functions_rpc.register_function( + rabbitmq_rpc_client=rpc_client, + function=mock_function, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + assert registered_function.uid is not None + + function_job = ProjectFunctionJob( + function_uid=registered_function.uid, + title="Test Function Job", + description="A test function job", + project_job_id=uuid4(), + inputs={"input1": "value1"}, + outputs={"output1": "result1"}, + ) + + # Register the function job + registered_job = await functions_rpc.register_function_job( + rabbitmq_rpc_client=rpc_client, + function_job=function_job, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + + # List function jobs + jobs, _ = await functions_rpc.list_function_jobs( + rabbitmq_rpc_client=rpc_client, + pagination_limit=10, + pagination_offset=0, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + + # Assert the list contains the registered job + assert len(jobs) > 0 + assert any(j.uid == registered_job.uid for j in jobs) + + +@pytest.mark.parametrize( + "user_role", + [UserRole.USER], +) +async def test_list_function_jobs_for_functionid( + client: TestClient, + rpc_client: RabbitMQRPCClient, + mock_function: ProjectFunction, + logged_user: UserInfoDict, + osparc_product_name: ProductName, +): + # Register the function first + first_registered_function = await functions_rpc.register_function( + rabbitmq_rpc_client=rpc_client, + function=mock_function, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + second_registered_function = await functions_rpc.register_function( + rabbitmq_rpc_client=rpc_client, + function=mock_function, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + + first_registered_function_jobs = [] + second_registered_function_jobs = [] + for i_job in range(6): + if i_job < 3: + function_job = ProjectFunctionJob( + function_uid=first_registered_function.uid, + title="Test Function Job", + description="A test function job", + project_job_id=uuid4(), + inputs={"input1": "value1"}, + outputs={"output1": "result1"}, + ) + # Register the function job + first_registered_function_jobs.append( + await functions_rpc.register_function_job( + rabbitmq_rpc_client=rpc_client, + function_job=function_job, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + ) + else: + function_job = ProjectFunctionJob( + function_uid=second_registered_function.uid, + title="Test Function Job", + description="A test function job", + project_job_id=uuid4(), + inputs={"input1": "value1"}, + outputs={"output1": "result1"}, + ) + # Register the function job + second_registered_function_jobs.append( + await functions_rpc.register_function_job( + rabbitmq_rpc_client=rpc_client, + function_job=function_job, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + ) + + # List function jobs for a specific function ID + jobs, _ = await functions_rpc.list_function_jobs( + rabbitmq_rpc_client=rpc_client, + pagination_limit=10, + pagination_offset=0, + filter_by_function_id=first_registered_function.uid, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + + # Assert the list contains the registered job + assert len(jobs) > 0 + assert len(jobs) == 3 + assert all(j.function_uid == first_registered_function.uid for j in jobs) + + +@pytest.mark.parametrize( + "user_role", + [UserRole.USER], +) +async def test_find_cached_function_jobs( + client: TestClient, + rpc_client: RabbitMQRPCClient, + logged_user: UserInfoDict, + other_logged_user: UserInfoDict, + osparc_product_name: ProductName, + mock_function: ProjectFunction, + clean_functions: None, +): + + # Register the function first + registered_function = await functions_rpc.register_function( + rabbitmq_rpc_client=rpc_client, + function=mock_function, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + + registered_function_jobs = [] + for value in range(5): + function_job = ProjectFunctionJob( + function_uid=registered_function.uid, + title="Test Function Job", + description="A test function job", + project_job_id=uuid4(), + inputs={"input1": value if value < 4 else 1}, + outputs={"output1": "result1"}, + ) + + # Register the function job + registered_job = await functions_rpc.register_function_job( + rabbitmq_rpc_client=rpc_client, + function_job=function_job, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + registered_function_jobs.append(registered_job) + + # Find cached function jobs + cached_jobs = await functions_rpc.find_cached_function_jobs( + rabbitmq_rpc_client=rpc_client, + function_id=registered_function.uid, + inputs={"input1": 1}, + user_id=logged_user["id"], + product_name=osparc_product_name, + ) + + # Assert the cached jobs contain the registered job + assert cached_jobs is not None + assert len(cached_jobs) == 2 + assert {job.uid for job in cached_jobs} == { + registered_function_jobs[1].uid, + registered_function_jobs[4].uid, + } + + cached_jobs = await functions_rpc.find_cached_function_jobs( + rabbitmq_rpc_client=rpc_client, + function_id=registered_function.uid, + inputs={"input1": 1}, + user_id=other_logged_user["id"], + product_name=osparc_product_name, + ) + + # Assert the cached jobs does not contain the registered job for the other user + assert cached_jobs is None diff --git a/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_functions_controller_rest.py b/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_functions_controller_rest.py index c01111c877d..5f4650a3f1e 100644 --- a/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_functions_controller_rest.py +++ b/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_functions_controller_rest.py @@ -21,6 +21,8 @@ from servicelib.aiohttp import status from simcore_service_webserver.db.models import UserRole +pytest_simcore_core_services_selection = ["rabbit"] + @pytest.fixture def mock_function() -> dict[str, Any]: diff --git a/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_functions_controller_rpc.py b/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_functions_controller_rpc.py index 86c32d56dde..c6f2fecc710 100644 --- a/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_functions_controller_rpc.py +++ b/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_functions_controller_rpc.py @@ -1,98 +1,33 @@ # pylint: disable=redefined-outer-name # pylint: disable=unused-argument -from collections.abc import Awaitable, Callable +import datetime from uuid import uuid4 import pytest from aiohttp.test_utils import TestClient from common_library.users_enums import UserRole from models_library.api_schemas_webserver.functions import ( - Function, - FunctionIDString, - FunctionJobCollection, JSONFunctionInputSchema, JSONFunctionOutputSchema, ProjectFunction, - ProjectFunctionJob, ) -from models_library.functions import FunctionJobCollectionsListFilters # import simcore_service_webserver.functions._functions_controller_rpc as functions_rpc from models_library.functions_errors import ( FunctionIDNotFoundError, - FunctionJobCollectionReadAccessDeniedError, - FunctionJobIDNotFoundError, - FunctionJobReadAccessDeniedError, FunctionReadAccessDeniedError, ) from models_library.products import ProductName -from pytest_simcore.helpers.monkeypatch_envs import setenvs_from_dict -from pytest_simcore.helpers.typing_env import EnvVarsDict from pytest_simcore.helpers.webserver_login import UserInfoDict from servicelib.rabbitmq import RabbitMQRPCClient from servicelib.rabbitmq.rpc_interfaces.webserver.functions import ( functions_rpc_interface as functions_rpc, ) -from settings_library.rabbit import RabbitSettings -from simcore_service_webserver.application_settings import ApplicationSettings pytest_simcore_core_services_selection = ["rabbit"] -@pytest.fixture -def app_environment( - rabbit_service: RabbitSettings, - app_environment: EnvVarsDict, - monkeypatch: pytest.MonkeyPatch, -) -> EnvVarsDict: - new_envs = setenvs_from_dict( - monkeypatch, - { - **app_environment, - "RABBIT_HOST": rabbit_service.RABBIT_HOST, - "RABBIT_PORT": f"{rabbit_service.RABBIT_PORT}", - "RABBIT_USER": rabbit_service.RABBIT_USER, - "RABBIT_SECURE": f"{rabbit_service.RABBIT_SECURE}", - "RABBIT_PASSWORD": rabbit_service.RABBIT_PASSWORD.get_secret_value(), - }, - ) - - settings = ApplicationSettings.create_from_envs() - assert settings.WEBSERVER_RABBITMQ - - return new_envs - - -@pytest.fixture -async def rpc_client( - rabbitmq_rpc_client: Callable[[str], Awaitable[RabbitMQRPCClient]], -) -> RabbitMQRPCClient: - return await rabbitmq_rpc_client("client") - - -@pytest.fixture -def mock_function() -> Function: - return ProjectFunction( - title="Test Function", - description="A test function", - input_schema=JSONFunctionInputSchema( - schema_content={ - "type": "object", - "properties": {"input1": {"type": "string"}}, - } - ), - output_schema=JSONFunctionOutputSchema( - schema_content={ - "type": "object", - "properties": {"output1": {"type": "string"}}, - } - ), - project_id=uuid4(), - default_inputs=None, - ) - - @pytest.mark.parametrize( "user_role", [UserRole.USER], @@ -114,6 +49,10 @@ async def test_register_get_delete_function( product_name=osparc_product_name, ) assert registered_function.uid is not None + assert registered_function.created_at - datetime.datetime.now( + datetime.UTC + ) < datetime.timedelta(seconds=60) + # Retrieve the function from the repository to verify it was saved saved_function = await functions_rpc.get_function( rabbitmq_rpc_client=rpc_client, @@ -122,14 +61,6 @@ async def test_register_get_delete_function( product_name=osparc_product_name, ) - with pytest.raises(FunctionReadAccessDeniedError): - await functions_rpc.get_function( - rabbitmq_rpc_client=rpc_client, - function_id=registered_function.uid, - user_id=other_logged_user["id"], - product_name=osparc_product_name, - ) - # Assert the saved function matches the input function assert saved_function.uid is not None assert saved_function.title == mock_function.title @@ -138,6 +69,7 @@ async def test_register_get_delete_function( # Ensure saved_function is of type ProjectFunction before accessing project_id assert isinstance(saved_function, ProjectFunction) assert saved_function.project_id == mock_function.project_id + assert saved_function.created_at == registered_function.created_at # Assert the returned function matches the expected result assert registered_function.title == mock_function.title @@ -145,6 +77,14 @@ async def test_register_get_delete_function( assert isinstance(registered_function, ProjectFunction) assert registered_function.project_id == mock_function.project_id + with pytest.raises(FunctionReadAccessDeniedError): + await functions_rpc.get_function( + rabbitmq_rpc_client=rpc_client, + function_id=registered_function.uid, + user_id=other_logged_user["id"], + product_name=osparc_product_name, + ) + with pytest.raises(FunctionReadAccessDeniedError): # Attempt to delete the function by another user await functions_rpc.delete_function( @@ -557,642 +497,3 @@ async def test_delete_function( product_name=osparc_product_name, ) assert registered_function.uid is not None - - -@pytest.mark.parametrize( - "user_role", - [UserRole.USER], -) -async def test_register_get_delete_function_job( - client: TestClient, - rpc_client: RabbitMQRPCClient, - mock_function: ProjectFunction, - logged_user: UserInfoDict, - other_logged_user: UserInfoDict, - osparc_product_name: ProductName, -): - # Register the function first - registered_function = await functions_rpc.register_function( - rabbitmq_rpc_client=rpc_client, - function=mock_function, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - assert registered_function.uid is not None - - function_job = ProjectFunctionJob( - function_uid=registered_function.uid, - title="Test Function Job", - description="A test function job", - project_job_id=uuid4(), - inputs={"input1": "value1"}, - outputs={"output1": "result1"}, - ) - - # Register the function job - registered_job = await functions_rpc.register_function_job( - rabbitmq_rpc_client=rpc_client, - function_job=function_job, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - - # Assert the registered job matches the input job - assert registered_job.function_uid == function_job.function_uid - assert registered_job.inputs == function_job.inputs - assert registered_job.outputs == function_job.outputs - - # Retrieve the function job using its ID - retrieved_job = await functions_rpc.get_function_job( - rabbitmq_rpc_client=rpc_client, - function_job_id=registered_job.uid, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - - # Assert the retrieved job matches the registered job - assert retrieved_job.function_uid == registered_job.function_uid - assert retrieved_job.inputs == registered_job.inputs - assert retrieved_job.outputs == registered_job.outputs - - # Test denied access for another user - with pytest.raises(FunctionJobReadAccessDeniedError): - await functions_rpc.get_function_job( - rabbitmq_rpc_client=rpc_client, - function_job_id=registered_job.uid, - user_id=other_logged_user["id"], - product_name=osparc_product_name, - ) - - # Test denied access for anothe product - with pytest.raises(FunctionJobReadAccessDeniedError): - await functions_rpc.get_function_job( - rabbitmq_rpc_client=rpc_client, - function_job_id=registered_job.uid, - user_id=other_logged_user["id"], - product_name="this_is_not_osparc", - ) - - with pytest.raises(FunctionJobReadAccessDeniedError): - # Attempt to delete the function job by another user - await functions_rpc.delete_function_job( - rabbitmq_rpc_client=rpc_client, - function_job_id=registered_job.uid, - user_id=other_logged_user["id"], - product_name=osparc_product_name, - ) - - # Delete the function job using its ID - await functions_rpc.delete_function_job( - rabbitmq_rpc_client=rpc_client, - function_job_id=registered_job.uid, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - - # Attempt to retrieve the deleted job - with pytest.raises(FunctionJobIDNotFoundError): - await functions_rpc.get_function_job( - rabbitmq_rpc_client=rpc_client, - function_job_id=registered_job.uid, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - - -@pytest.mark.parametrize( - "user_role", - [UserRole.USER], -) -async def test_get_function_job_not_found( - client: TestClient, - rpc_client: RabbitMQRPCClient, - logged_user: UserInfoDict, - osparc_product_name: ProductName, - clean_functions: None, -): - # Attempt to retrieve a function job that does not exist - with pytest.raises(FunctionJobIDNotFoundError): - await functions_rpc.get_function_job( - rabbitmq_rpc_client=rpc_client, - function_job_id=uuid4(), - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - - -@pytest.mark.parametrize( - "user_role", - [UserRole.USER], -) -async def test_list_function_jobs( - client: TestClient, - rpc_client: RabbitMQRPCClient, - mock_function: ProjectFunction, - logged_user: UserInfoDict, - osparc_product_name: ProductName, -): - # Register the function first - registered_function = await functions_rpc.register_function( - rabbitmq_rpc_client=rpc_client, - function=mock_function, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - assert registered_function.uid is not None - - function_job = ProjectFunctionJob( - function_uid=registered_function.uid, - title="Test Function Job", - description="A test function job", - project_job_id=uuid4(), - inputs={"input1": "value1"}, - outputs={"output1": "result1"}, - ) - - # Register the function job - registered_job = await functions_rpc.register_function_job( - rabbitmq_rpc_client=rpc_client, - function_job=function_job, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - - # List function jobs - jobs, _ = await functions_rpc.list_function_jobs( - rabbitmq_rpc_client=rpc_client, - pagination_limit=10, - pagination_offset=0, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - - # Assert the list contains the registered job - assert len(jobs) > 0 - assert any(j.uid == registered_job.uid for j in jobs) - - -@pytest.mark.parametrize( - "user_role", - [UserRole.USER], -) -async def test_list_function_jobs_for_functionid( - client: TestClient, - rpc_client: RabbitMQRPCClient, - mock_function: ProjectFunction, - logged_user: UserInfoDict, - osparc_product_name: ProductName, -): - # Register the function first - first_registered_function = await functions_rpc.register_function( - rabbitmq_rpc_client=rpc_client, - function=mock_function, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - second_registered_function = await functions_rpc.register_function( - rabbitmq_rpc_client=rpc_client, - function=mock_function, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - - first_registered_function_jobs = [] - second_registered_function_jobs = [] - for i_job in range(6): - if i_job < 3: - function_job = ProjectFunctionJob( - function_uid=first_registered_function.uid, - title="Test Function Job", - description="A test function job", - project_job_id=uuid4(), - inputs={"input1": "value1"}, - outputs={"output1": "result1"}, - ) - # Register the function job - first_registered_function_jobs.append( - await functions_rpc.register_function_job( - rabbitmq_rpc_client=rpc_client, - function_job=function_job, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - ) - else: - function_job = ProjectFunctionJob( - function_uid=second_registered_function.uid, - title="Test Function Job", - description="A test function job", - project_job_id=uuid4(), - inputs={"input1": "value1"}, - outputs={"output1": "result1"}, - ) - # Register the function job - second_registered_function_jobs.append( - await functions_rpc.register_function_job( - rabbitmq_rpc_client=rpc_client, - function_job=function_job, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - ) - - # List function jobs for a specific function ID - jobs, _ = await functions_rpc.list_function_jobs( - rabbitmq_rpc_client=rpc_client, - pagination_limit=10, - pagination_offset=0, - filter_by_function_id=first_registered_function.uid, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - - # Assert the list contains the registered job - assert len(jobs) > 0 - assert len(jobs) == 3 - assert all(j.function_uid == first_registered_function.uid for j in jobs) - - -@pytest.mark.parametrize( - "user_role", - [UserRole.USER], -) -async def test_function_job_collection( - client: TestClient, - mock_function: ProjectFunction, - rpc_client: RabbitMQRPCClient, - logged_user: UserInfoDict, - other_logged_user: UserInfoDict, - osparc_product_name: ProductName, -): - # Register the function first - registered_function = await functions_rpc.register_function( - rabbitmq_rpc_client=rpc_client, - function=mock_function, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - assert registered_function.uid is not None - - registered_function_job = ProjectFunctionJob( - function_uid=registered_function.uid, - title="Test Function Job", - description="A test function job", - project_job_id=uuid4(), - inputs={"input1": "value1"}, - outputs={"output1": "result1"}, - ) - # Register the function job - function_job_ids = [] - for _ in range(3): - registered_function_job = ProjectFunctionJob( - function_uid=registered_function.uid, - title="Test Function Job", - description="A test function job", - project_job_id=uuid4(), - inputs={"input1": "value1"}, - outputs={"output1": "result1"}, - ) - # Register the function job - registered_job = await functions_rpc.register_function_job( - rabbitmq_rpc_client=rpc_client, - function_job=registered_function_job, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - assert registered_job.uid is not None - function_job_ids.append(registered_job.uid) - - function_job_collection = FunctionJobCollection( - title="Test Function Job Collection", - description="A test function job collection", - job_ids=function_job_ids, - ) - - # Register the function job collection - registered_collection = await functions_rpc.register_function_job_collection( - rabbitmq_rpc_client=rpc_client, - function_job_collection=function_job_collection, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - assert registered_collection.uid is not None - - # Get the function job collection - retrieved_collection = await functions_rpc.get_function_job_collection( - rabbitmq_rpc_client=rpc_client, - function_job_collection_id=registered_collection.uid, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - assert retrieved_collection.uid == registered_collection.uid - assert registered_collection.job_ids == function_job_ids - - # Test denied access for another user - with pytest.raises(FunctionJobCollectionReadAccessDeniedError): - await functions_rpc.get_function_job_collection( - rabbitmq_rpc_client=rpc_client, - function_job_collection_id=registered_collection.uid, - user_id=other_logged_user["id"], - product_name=osparc_product_name, - ) - - # Test denied access for another product - with pytest.raises(FunctionJobCollectionReadAccessDeniedError): - await functions_rpc.get_function_job_collection( - rabbitmq_rpc_client=rpc_client, - function_job_collection_id=registered_collection.uid, - user_id=other_logged_user["id"], - product_name="this_is_not_osparc", - ) - - # Attempt to delete the function job collection by another user - with pytest.raises(FunctionJobCollectionReadAccessDeniedError): - await functions_rpc.delete_function_job_collection( - rabbitmq_rpc_client=rpc_client, - function_job_collection_id=registered_collection.uid, - user_id=other_logged_user["id"], - product_name=osparc_product_name, - ) - - await functions_rpc.delete_function_job_collection( - rabbitmq_rpc_client=rpc_client, - function_job_collection_id=registered_collection.uid, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - # Attempt to retrieve the deleted collection - with pytest.raises(FunctionJobIDNotFoundError): - await functions_rpc.get_function_job( - rabbitmq_rpc_client=rpc_client, - function_job_id=registered_collection.uid, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - - -@pytest.mark.parametrize( - "user_role", - [UserRole.USER], -) -async def test_list_function_job_collections( - client: TestClient, - mock_function: ProjectFunction, - rpc_client: RabbitMQRPCClient, - clean_functions: None, - clean_function_job_collections: None, - logged_user: UserInfoDict, - osparc_product_name: ProductName, -): - # List function job collections when none are registered - collections, page_meta = await functions_rpc.list_function_job_collections( - rabbitmq_rpc_client=rpc_client, - pagination_limit=10, - pagination_offset=0, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - - # Assert the list is empty - assert page_meta.count == 0 - assert page_meta.total == 0 - assert page_meta.offset == 0 - assert len(collections) == 0 - - # Register the function first - registered_function = await functions_rpc.register_function( - rabbitmq_rpc_client=rpc_client, - function=mock_function, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - assert registered_function.uid is not None - - # Create a function job collection - function_job_ids = [] - for _ in range(3): - registered_function_job = ProjectFunctionJob( - function_uid=registered_function.uid, - title="Test Function Job", - description="A test function job", - project_job_id=uuid4(), - inputs={"input1": "value1"}, - outputs={"output1": "result1"}, - ) - # Register the function job - registered_job = await functions_rpc.register_function_job( - rabbitmq_rpc_client=rpc_client, - function_job=registered_function_job, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - assert registered_job.uid is not None - function_job_ids.append(registered_job.uid) - - function_job_collection = FunctionJobCollection( - title="Test Function Job Collection", - description="A test function job collection", - job_ids=function_job_ids, - ) - - # Register the function job collection - registered_collections = [ - await functions_rpc.register_function_job_collection( - rabbitmq_rpc_client=rpc_client, - function_job_collection=function_job_collection, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - for _ in range(3) - ] - assert all( - registered_collection.uid is not None - for registered_collection in registered_collections - ) - - # List function job collections - collections, page_params = await functions_rpc.list_function_job_collections( - rabbitmq_rpc_client=rpc_client, - pagination_limit=2, - pagination_offset=1, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - - # Assert the list contains the registered collection - assert page_params.count == 2 - assert page_params.total == 3 - assert page_params.offset == 1 - assert len(collections) == 2 - assert collections[0].uid in [ - collection.uid for collection in registered_collections - ] - assert collections[1].uid in [ - collection.uid for collection in registered_collections - ] - - -@pytest.mark.parametrize( - "user_role", - [UserRole.USER], -) -async def test_list_function_job_collections_filtered_function_id( - client: TestClient, - rpc_client: RabbitMQRPCClient, - mock_function: ProjectFunction, - clean_functions: None, - clean_function_job_collections: None, - logged_user: UserInfoDict, - osparc_product_name: ProductName, -): - # Register the function first - registered_function = await functions_rpc.register_function( - rabbitmq_rpc_client=rpc_client, - function=mock_function, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - other_registered_function = await functions_rpc.register_function( - rabbitmq_rpc_client=rpc_client, - function=mock_function, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - - registered_collections = [] - for coll_i in range(5): - if coll_i < 3: - function_id = registered_function.uid - else: - function_id = other_registered_function.uid - # Create a function job collection - function_job_ids = [] - for _ in range(3): - registered_function_job = ProjectFunctionJob( - function_uid=function_id, - title="Test Function Job", - description="A test function job", - project_job_id=uuid4(), - inputs={"input1": "value1"}, - outputs={"output1": "result1"}, - ) - # Register the function job - registered_job = await functions_rpc.register_function_job( - rabbitmq_rpc_client=rpc_client, - function_job=registered_function_job, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - assert registered_job.uid is not None - function_job_ids.append(registered_job.uid) - - function_job_collection = FunctionJobCollection( - title="Test Function Job Collection", - description="A test function job collection", - job_ids=function_job_ids, - ) - - # Register the function job collection - registered_collection = await functions_rpc.register_function_job_collection( - rabbitmq_rpc_client=rpc_client, - function_job_collection=function_job_collection, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - registered_collections.append(registered_collection) - - # List function job collections with a specific function ID - collections, page_meta = await functions_rpc.list_function_job_collections( - rabbitmq_rpc_client=rpc_client, - pagination_limit=10, - pagination_offset=1, - filters=FunctionJobCollectionsListFilters( - has_function_id=FunctionIDString(registered_function.uid) - ), - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - - # Assert the list contains the registered collection - assert page_meta.count == 2 - assert page_meta.total == 3 - assert page_meta.offset == 1 - - assert len(collections) == 2 - assert collections[0].uid in [ - collection.uid for collection in registered_collections - ] - assert collections[1].uid in [ - collection.uid for collection in registered_collections - ] - - -@pytest.mark.parametrize( - "user_role", - [UserRole.USER], -) -async def test_find_cached_function_jobs( - client: TestClient, - rpc_client: RabbitMQRPCClient, - logged_user: UserInfoDict, - other_logged_user: UserInfoDict, - osparc_product_name: ProductName, - mock_function: ProjectFunction, - clean_functions: None, -): - - # Register the function first - registered_function = await functions_rpc.register_function( - rabbitmq_rpc_client=rpc_client, - function=mock_function, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - - registered_function_jobs = [] - for value in range(5): - function_job = ProjectFunctionJob( - function_uid=registered_function.uid, - title="Test Function Job", - description="A test function job", - project_job_id=uuid4(), - inputs={"input1": value if value < 4 else 1}, - outputs={"output1": "result1"}, - ) - - # Register the function job - registered_job = await functions_rpc.register_function_job( - rabbitmq_rpc_client=rpc_client, - function_job=function_job, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - registered_function_jobs.append(registered_job) - - # Find cached function jobs - cached_jobs = await functions_rpc.find_cached_function_jobs( - rabbitmq_rpc_client=rpc_client, - function_id=registered_function.uid, - inputs={"input1": 1}, - user_id=logged_user["id"], - product_name=osparc_product_name, - ) - - # Assert the cached jobs contain the registered job - assert cached_jobs is not None - assert len(cached_jobs) == 2 - assert {job.uid for job in cached_jobs} == { - registered_function_jobs[1].uid, - registered_function_jobs[4].uid, - } - - cached_jobs = await functions_rpc.find_cached_function_jobs( - rabbitmq_rpc_client=rpc_client, - function_id=registered_function.uid, - inputs={"input1": 1}, - user_id=other_logged_user["id"], - product_name=osparc_product_name, - ) - - # Assert the cached jobs does not contain the registered job for the other user - assert cached_jobs is None From 5ebc54c6b8ad41b6d5fa2376e1fce4b478d2e40f Mon Sep 17 00:00:00 2001 From: Werner Van Geit Date: Tue, 3 Jun 2025 14:57:17 +0200 Subject: [PATCH 5/8] Update openapi specs --- services/api-server/openapi.json | 46 ++++++++++++++++++- .../api/v0/openapi.yaml | 10 ++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/services/api-server/openapi.json b/services/api-server/openapi.json index c4ad831452d..495ba12f25c 100644 --- a/services/api-server/openapi.json +++ b/services/api-server/openapi.json @@ -10093,11 +10093,17 @@ "type": "string", "format": "uuid", "title": "Uid" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" } }, "type": "object", "required": [ - "uid" + "uid", + "created_at" ], "title": "RegisteredFunctionJobCollection" }, @@ -10163,6 +10169,11 @@ "format": "uuid", "title": "Uid" }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, "project_id": { "type": "string", "format": "uuid", @@ -10175,6 +10186,7 @@ "output_schema", "default_inputs", "uid", + "created_at", "project_id" ], "title": "RegisteredProjectFunction" @@ -10229,6 +10241,11 @@ "format": "uuid", "title": "Uid" }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, "project_job_id": { "type": "string", "format": "uuid", @@ -10241,6 +10258,7 @@ "inputs", "outputs", "uid", + "created_at", "project_job_id" ], "title": "RegisteredProjectFunctionJob" @@ -10307,6 +10325,11 @@ "format": "uuid", "title": "Uid" }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, "code_url": { "type": "string", "title": "Code Url" @@ -10318,6 +10341,7 @@ "output_schema", "default_inputs", "uid", + "created_at", "code_url" ], "title": "RegisteredPythonCodeFunction" @@ -10371,6 +10395,11 @@ "type": "string", "format": "uuid", "title": "Uid" + }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" } }, "type": "object", @@ -10378,7 +10407,8 @@ "function_uid", "inputs", "outputs", - "uid" + "uid", + "created_at" ], "title": "RegisteredPythonCodeFunctionJob" }, @@ -10444,6 +10474,11 @@ "format": "uuid", "title": "Uid" }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, "solver_key": { "type": "string", "pattern": "^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$", @@ -10461,6 +10496,7 @@ "output_schema", "default_inputs", "uid", + "created_at", "solver_key", "solver_version" ], @@ -10516,6 +10552,11 @@ "format": "uuid", "title": "Uid" }, + "created_at": { + "type": "string", + "format": "date-time", + "title": "Created At" + }, "solver_job_id": { "type": "string", "format": "uuid", @@ -10528,6 +10569,7 @@ "inputs", "outputs", "uid", + "created_at", "solver_job_id" ], "title": "RegisteredSolverFunctionJob" diff --git a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml index eadb6dcc1f9..e65606826b8 100644 --- a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml @@ -15462,6 +15462,10 @@ components: type: string format: uuid title: Uid + createdAt: + type: string + format: date-time + title: Createdat projectId: type: string format: uuid @@ -15472,6 +15476,7 @@ components: - outputSchema - defaultInputs - uid + - createdAt - projectId title: RegisteredProjectFunctionGet RegisteredSolverFunctionGet: @@ -15514,6 +15519,10 @@ components: type: string format: uuid title: Uid + createdAt: + type: string + format: date-time + title: Createdat solverKey: type: string pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ @@ -15528,6 +15537,7 @@ components: - outputSchema - defaultInputs - uid + - createdAt - solverKey - solverVersion title: RegisteredSolverFunctionGet From 15467398ce9d8e305a35ae39df99e659b26d2c4d Mon Sep 17 00:00:00 2001 From: Werner Van Geit Date: Wed, 4 Jun 2025 09:54:26 +0200 Subject: [PATCH 6/8] =?UTF-8?q?services/api-server=20version:=200.8.0=20?= =?UTF-8?q?=E2=86=92=200.9.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- services/api-server/VERSION | 2 +- services/api-server/setup.cfg | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/services/api-server/VERSION b/services/api-server/VERSION index a3df0a6959e..ac39a106c48 100644 --- a/services/api-server/VERSION +++ b/services/api-server/VERSION @@ -1 +1 @@ -0.8.0 +0.9.0 diff --git a/services/api-server/setup.cfg b/services/api-server/setup.cfg index 8bb3e9b8f8e..21f72b9aecc 100644 --- a/services/api-server/setup.cfg +++ b/services/api-server/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.8.0 +current_version = 0.9.0 commit = True message = services/api-server version: {current_version} → {new_version} tag = False @@ -11,12 +11,12 @@ commit_args = --no-verify asyncio_mode = auto asyncio_default_fixture_loop_scope = function addopts = --strict-markers -markers = +markers = slow: marks tests as slow (deselect with '-m "not slow"') acceptance_test: "marks tests as 'acceptance tests' i.e. does the system do what the user expects? Typically those are workflows." testit: "marks test to run during development" [mypy] -plugins = +plugins = pydantic.mypy sqlalchemy.ext.mypy.plugin From deee2ee3020ea08803de69c1b9ac354b67d18a62 Mon Sep 17 00:00:00 2001 From: Werner Van Geit Date: Wed, 4 Jun 2025 12:51:21 +0200 Subject: [PATCH 7/8] Increase api version number --- services/api-server/openapi.json | 24 ++--- .../routes/function_job_collections_routes.py | 30 +++++- .../api/routes/function_jobs_routes.py | 40 ++++++-- .../api/routes/functions_routes.py | 91 +++++++++++-------- .../api/routes/programs.py | 8 +- .../api/routes/solvers.py | 8 +- .../api/routes/solvers_jobs_read.py | 4 +- .../api/routes/studies_jobs.py | 16 ++-- ...function_job_collections_controller_rpc.py | 2 - .../test_function_jobs_controller_rpc.py | 2 - 10 files changed, 141 insertions(+), 84 deletions(-) diff --git a/services/api-server/openapi.json b/services/api-server/openapi.json index 495ba12f25c..81de55be4c3 100644 --- a/services/api-server/openapi.json +++ b/services/api-server/openapi.json @@ -3,7 +3,7 @@ "info": { "title": "osparc.io public API", "description": "osparc-simcore public API specifications", - "version": "0.8.0" + "version": "0.9.0" }, "paths": { "/v0/meta": { @@ -5286,7 +5286,7 @@ "function_jobs" ], "summary": "List Function Jobs", - "description": "List function jobs\n\nNew in *version 0.8.0*", + "description": "List function jobs\n\nNew in *version 0.8.0*\n\nAdded in *version 0.9.0*: add `created_at` field in the registered function-related objects", "operationId": "list_function_jobs", "security": [ { @@ -5346,7 +5346,7 @@ "function_jobs" ], "summary": "Register Function Job", - "description": "Create function job\n\nNew in *version 0.8.0*", + "description": "Create function job\n\nNew in *version 0.8.0*\n\nAdded in *version 0.9.0*: add `created_at` field in the registered function-related objects", "operationId": "register_function_job", "security": [ { @@ -5431,7 +5431,7 @@ "function_jobs" ], "summary": "Get Function Job", - "description": "Get function job\n\nNew in *version 0.8.0*", + "description": "Get function job\n\nNew in *version 0.8.0*\n\nAdded in *version 0.9.0*: add `created_at` field in the registered function-related objects", "operationId": "get_function_job", "security": [ { @@ -6810,7 +6810,7 @@ "functions" ], "summary": "Register Function", - "description": "Create function\n\nNew in *version 0.8.0*", + "description": "Create function\n\nNew in *version 0.8.0*\n\nAdded in *version 0.9.0*: add `created_at` field in the registered function-related objects", "operationId": "register_function", "security": [ { @@ -6903,7 +6903,7 @@ "functions" ], "summary": "List Functions", - "description": "List functions\n\nNew in *version 0.8.0*", + "description": "List functions\n\nNew in *version 0.8.0*\n\nAdded in *version 0.9.0*: add `created_at` field in the registered function-related objects", "operationId": "list_functions", "security": [ { @@ -6965,7 +6965,7 @@ "functions" ], "summary": "Get Function", - "description": "Get function\n\nNew in *version 0.8.0*", + "description": "Get function\n\nNew in *version 0.8.0*\n\nAdded in *version 0.9.0*: add `created_at` field in the registered function-related objects", "operationId": "get_function", "security": [ { @@ -7098,7 +7098,7 @@ "functions" ], "summary": "List Function Jobs For Functionid", - "description": "List function jobs for a function\n\nNew in *version 0.8.0*", + "description": "List function jobs for a function\n\nNew in *version 0.8.0*\n\nAdded in *version 0.9.0*: add `created_at` field in the registered function-related objects", "operationId": "list_function_jobs_for_functionid", "security": [ { @@ -7170,7 +7170,7 @@ "functions" ], "summary": "Update Function Title", - "description": "Update function\n\nNew in *version 0.8.0*", + "description": "Update function\n\nNew in *version 0.8.0*\n\nAdded in *version 0.9.0*: add `created_at` field in the registered function-related objects", "operationId": "update_function_title", "security": [ { @@ -7257,7 +7257,7 @@ "functions" ], "summary": "Update Function Description", - "description": "Update function\n\nNew in *version 0.8.0*", + "description": "Update function\n\nNew in *version 0.8.0*\n\nAdded in *version 0.9.0*: add `created_at` field in the registered function-related objects", "operationId": "update_function_description", "security": [ { @@ -7568,7 +7568,7 @@ "functions" ], "summary": "Run Function", - "description": "Run function\n\nNew in *version 0.8.0*", + "description": "Run function\n\nNew in *version 0.8.0*\n\nAdded in *version 0.9.0*: add `created_at` field in the registered function-related objects", "operationId": "run_function", "security": [ { @@ -7664,7 +7664,7 @@ "functions" ], "summary": "Map Function", - "description": "Map function over input parameters\n\nNew in *version 0.8.0*", + "description": "Map function over input parameters\n\nNew in *version 0.8.0*\n\nAdded in *version 0.9.0*: add `created_at` field in the registered function-related objects", "operationId": "map_function", "security": [ { diff --git a/services/api-server/src/simcore_service_api_server/api/routes/function_job_collections_routes.py b/services/api-server/src/simcore_service_api_server/api/routes/function_job_collections_routes.py index bc975354e53..eb50f8bd0ef 100644 --- a/services/api-server/src/simcore_service_api_server/api/routes/function_job_collections_routes.py +++ b/services/api-server/src/simcore_service_api_server/api/routes/function_job_collections_routes.py @@ -24,7 +24,11 @@ ) from ..dependencies.services import get_api_client from ..dependencies.webserver_rpc import get_wb_api_rpc_client -from ._constants import FMSG_CHANGELOG_NEW_IN_VERSION, create_route_description +from ._constants import ( + FMSG_CHANGELOG_ADDED_IN_VERSION, + FMSG_CHANGELOG_NEW_IN_VERSION, + create_route_description, +) from .function_jobs_routes import function_job_status, get_function_job # pylint: disable=too-many-arguments @@ -41,6 +45,30 @@ }, } +ENDPOINTS = [ + "list_function_job_collections", + "register_function_job_collection", + "get_function_job_collection", + "delete_function_job_collection", +] +CHANGE_LOGS = {} +for endpoint in ENDPOINTS: + CHANGE_LOGS[endpoint] = [ + FMSG_CHANGELOG_NEW_IN_VERSION.format("0.8.0"), + ] + if endpoint in [ + "list_function_job_collections", + "register_function_job_collection", + "get_function_job_collection", + "function_job_collection_list_function_jobs", + ]: + CHANGE_LOGS[endpoint].append( + FMSG_CHANGELOG_ADDED_IN_VERSION.format( + "0.9.0", + "add `created_at` field in the registered function-related objects", + ) + ) + @function_job_collections_router.get( "", diff --git a/services/api-server/src/simcore_service_api_server/api/routes/function_jobs_routes.py b/services/api-server/src/simcore_service_api_server/api/routes/function_jobs_routes.py index ea39cb05ab0..cd461b95fb3 100644 --- a/services/api-server/src/simcore_service_api_server/api/routes/function_jobs_routes.py +++ b/services/api-server/src/simcore_service_api_server/api/routes/function_jobs_routes.py @@ -31,7 +31,11 @@ from ..dependencies.webserver_http import get_webserver_session from ..dependencies.webserver_rpc import get_wb_api_rpc_client from . import solvers_jobs, solvers_jobs_read, studies_jobs -from ._constants import FMSG_CHANGELOG_NEW_IN_VERSION, create_route_description +from ._constants import ( + FMSG_CHANGELOG_ADDED_IN_VERSION, + FMSG_CHANGELOG_NEW_IN_VERSION, + create_route_description, +) # pylint: disable=too-many-arguments # pylint: disable=cyclic-import @@ -46,15 +50,33 @@ }, } -FIRST_RELEASE_VERSION = "0.8.0" +ENDPOINTS = [ + "list_function_jobs", + "register_function_job", + "get_function_job", + "delete_function_job", + "function_job_status", + "function_job_outputs", +] +CHANGE_LOGS = {} +for endpoint in ENDPOINTS: + CHANGE_LOGS[endpoint] = [ + FMSG_CHANGELOG_NEW_IN_VERSION.format("0.8.0"), + ] + if endpoint in ["list_function_jobs", "register_function_job", "get_function_job"]: + CHANGE_LOGS[endpoint].append( + FMSG_CHANGELOG_ADDED_IN_VERSION.format( + "0.9.0", + "add `created_at` field in the registered function-related objects", + ) + ) @function_job_router.get( "", response_model=Page[RegisteredFunctionJob], description=create_route_description( - base="List function jobs", - changelog=[FMSG_CHANGELOG_NEW_IN_VERSION.format(FIRST_RELEASE_VERSION)], + base="List function jobs", changelog=CHANGE_LOGS["list_function_jobs"] ), ) async def list_function_jobs( @@ -82,7 +104,7 @@ async def list_function_jobs( response_model=RegisteredFunctionJob, description=create_route_description( base="Create function job", - changelog=[FMSG_CHANGELOG_NEW_IN_VERSION.format(FIRST_RELEASE_VERSION)], + changelog=CHANGE_LOGS["register_function_job"], ), ) async def register_function_job( @@ -102,7 +124,7 @@ async def register_function_job( responses={**_COMMON_FUNCTION_JOB_ERROR_RESPONSES}, description=create_route_description( base="Get function job", - changelog=[FMSG_CHANGELOG_NEW_IN_VERSION.format(FIRST_RELEASE_VERSION)], + changelog=CHANGE_LOGS["get_function_job"], ), ) async def get_function_job( @@ -122,7 +144,7 @@ async def get_function_job( responses={**_COMMON_FUNCTION_JOB_ERROR_RESPONSES}, description=create_route_description( base="Delete function job", - changelog=[FMSG_CHANGELOG_NEW_IN_VERSION.format(FIRST_RELEASE_VERSION)], + changelog=CHANGE_LOGS["delete_function_job"], ), ) async def delete_function_job( @@ -142,7 +164,7 @@ async def delete_function_job( responses={**_COMMON_FUNCTION_JOB_ERROR_RESPONSES}, description=create_route_description( base="Get function job status", - changelog=[FMSG_CHANGELOG_NEW_IN_VERSION.format(FIRST_RELEASE_VERSION)], + changelog=CHANGE_LOGS["function_job_status"], ), ) async def function_job_status( @@ -222,7 +244,7 @@ async def get_function_from_functionjobid( responses={**_COMMON_FUNCTION_JOB_ERROR_RESPONSES}, description=create_route_description( base="Get function job outputs", - changelog=[FMSG_CHANGELOG_NEW_IN_VERSION.format(FIRST_RELEASE_VERSION)], + changelog=CHANGE_LOGS["function_job_outputs"], ), ) async def function_job_outputs( diff --git a/services/api-server/src/simcore_service_api_server/api/routes/functions_routes.py b/services/api-server/src/simcore_service_api_server/api/routes/functions_routes.py index 1cd933a1bb2..4551c313c16 100644 --- a/services/api-server/src/simcore_service_api_server/api/routes/functions_routes.py +++ b/services/api-server/src/simcore_service_api_server/api/routes/functions_routes.py @@ -43,7 +43,11 @@ from ..dependencies.webserver_http import get_webserver_session from ..dependencies.webserver_rpc import get_wb_api_rpc_client from . import solvers_jobs, studies_jobs -from ._constants import FMSG_CHANGELOG_NEW_IN_VERSION, create_route_description +from ._constants import ( + FMSG_CHANGELOG_ADDED_IN_VERSION, + FMSG_CHANGELOG_NEW_IN_VERSION, + create_route_description, +) from .function_jobs_routes import register_function_job # pylint: disable=too-many-arguments @@ -58,7 +62,42 @@ }, } -FIRST_RELEASE_VERSION = "0.8.0" + +ENDPOINTS = [ + "register_function", + "get_function", + "list_functions", + "list_function_jobs_for_functionid", + "update_function_title", + "update_function_description", + "get_function_inputschema", + "get_function_outputschema", + "validate_function_inputs", + "run_function", + "delete_function", + "map_function", +] +CHANGE_LOGS = {} +for endpoint in ENDPOINTS: + CHANGE_LOGS[endpoint] = [ + FMSG_CHANGELOG_NEW_IN_VERSION.format("0.8.0"), + ] + if endpoint in [ + "register_function", + "get_function", + "list_functions", + "list_function_jobs_for_functionid", + "update_function_title", + "update_function_description", + "run_function", + "map_function", + ]: + CHANGE_LOGS[endpoint].append( + FMSG_CHANGELOG_ADDED_IN_VERSION.format( + "0.9.0", + "add `created_at` field in the registered function-related objects", + ) + ) @function_router.post( @@ -67,9 +106,7 @@ responses={**_COMMON_FUNCTION_ERROR_RESPONSES}, description=create_route_description( base="Create function", - changelog=[ - FMSG_CHANGELOG_NEW_IN_VERSION.format(FIRST_RELEASE_VERSION), - ], + changelog=CHANGE_LOGS["register_function"], ), ) async def register_function( @@ -89,9 +126,7 @@ async def register_function( responses={**_COMMON_FUNCTION_ERROR_RESPONSES}, description=create_route_description( base="Get function", - changelog=[ - FMSG_CHANGELOG_NEW_IN_VERSION.format(FIRST_RELEASE_VERSION), - ], + changelog=CHANGE_LOGS["get_function"], ), ) async def get_function( @@ -110,9 +145,7 @@ async def get_function( response_model=Page[RegisteredFunction], description=create_route_description( base="List functions", - changelog=[ - FMSG_CHANGELOG_NEW_IN_VERSION.format("0.8.0"), - ], + changelog=CHANGE_LOGS["list_functions"], ), ) async def list_functions( @@ -140,9 +173,7 @@ async def list_functions( response_model=Page[RegisteredFunctionJob], description=create_route_description( base="List function jobs for a function", - changelog=[ - FMSG_CHANGELOG_NEW_IN_VERSION.format(FIRST_RELEASE_VERSION), - ], + changelog=CHANGE_LOGS["list_function_jobs_for_functionid"], ), ) async def list_function_jobs_for_functionid( @@ -173,9 +204,7 @@ async def list_function_jobs_for_functionid( responses={**_COMMON_FUNCTION_ERROR_RESPONSES}, description=create_route_description( base="Update function", - changelog=[ - FMSG_CHANGELOG_NEW_IN_VERSION.format(FIRST_RELEASE_VERSION), - ], + changelog=CHANGE_LOGS["update_function_title"], ), ) async def update_function_title( @@ -200,9 +229,7 @@ async def update_function_title( responses={**_COMMON_FUNCTION_ERROR_RESPONSES}, description=create_route_description( base="Update function", - changelog=[ - FMSG_CHANGELOG_NEW_IN_VERSION.format(FIRST_RELEASE_VERSION), - ], + changelog=CHANGE_LOGS["update_function_description"], ), ) async def update_function_description( @@ -244,9 +271,7 @@ def _join_inputs( responses={**_COMMON_FUNCTION_ERROR_RESPONSES}, description=create_route_description( base="Get function input schema", - changelog=[ - FMSG_CHANGELOG_NEW_IN_VERSION.format(FIRST_RELEASE_VERSION), - ], + changelog=CHANGE_LOGS["get_function_inputschema"], ), ) async def get_function_inputschema( @@ -267,9 +292,7 @@ async def get_function_inputschema( responses={**_COMMON_FUNCTION_ERROR_RESPONSES}, description=create_route_description( base="Get function output schema", - changelog=[ - FMSG_CHANGELOG_NEW_IN_VERSION.format(FIRST_RELEASE_VERSION), - ], + changelog=CHANGE_LOGS["get_function_outputschema"], ), ) async def get_function_outputschema( @@ -293,9 +316,7 @@ async def get_function_outputschema( }, description=create_route_description( base="Validate inputs against the function's input schema", - changelog=[ - FMSG_CHANGELOG_NEW_IN_VERSION.format(FIRST_RELEASE_VERSION), - ], + changelog=CHANGE_LOGS["validate_function_inputs"], ), ) async def validate_function_inputs( @@ -333,9 +354,7 @@ async def validate_function_inputs( responses={**_COMMON_FUNCTION_ERROR_RESPONSES}, description=create_route_description( base="Run function", - changelog=[ - FMSG_CHANGELOG_NEW_IN_VERSION.format(FIRST_RELEASE_VERSION), - ], + changelog=CHANGE_LOGS["run_function"], ), ) async def run_function( # noqa: PLR0913 @@ -470,9 +489,7 @@ async def run_function( # noqa: PLR0913 responses={**_COMMON_FUNCTION_ERROR_RESPONSES}, description=create_route_description( base="Delete function", - changelog=[ - FMSG_CHANGELOG_NEW_IN_VERSION.format(FIRST_RELEASE_VERSION), - ], + changelog=CHANGE_LOGS["delete_function"], ), ) async def delete_function( @@ -500,9 +517,7 @@ async def delete_function( responses={**_COMMON_FUNCTION_ERROR_RESPONSES}, description=create_route_description( base="Map function over input parameters", - changelog=[ - FMSG_CHANGELOG_NEW_IN_VERSION.format(FIRST_RELEASE_VERSION), - ], + changelog=CHANGE_LOGS["map_function"], ), ) async def map_function( # noqa: PLR0913 diff --git a/services/api-server/src/simcore_service_api_server/api/routes/programs.py b/services/api-server/src/simcore_service_api_server/api/routes/programs.py index 0f3de634193..2ccd9d5ed7b 100644 --- a/services/api-server/src/simcore_service_api_server/api/routes/programs.py +++ b/services/api-server/src/simcore_service_api_server/api/routes/programs.py @@ -42,10 +42,10 @@ description=create_route_description( base="Lists the latest of all available programs", changelog=[ - FMSG_CHANGELOG_NEW_IN_VERSION.format("0.9"), + FMSG_CHANGELOG_NEW_IN_VERSION.format("0.10"), ], ), - include_in_schema=False, # TO BE RELEASED in 0.9 + include_in_schema=False, # TO BE RELEASED in 0.10 ) async def list_programs( program_service: Annotated[ProgramService, Depends(get_program_service)], @@ -77,10 +77,10 @@ async def list_programs( description=create_route_description( base="Lists the latest of all available programs", changelog=[ - FMSG_CHANGELOG_NEW_IN_VERSION.format("0.9"), + FMSG_CHANGELOG_NEW_IN_VERSION.format("0.10"), ], ), - include_in_schema=False, # TO BE RELEASED in 0.9 + include_in_schema=False, # TO BE RELEASED in 0.10 ) async def list_program_history( program_key: ProgramKeyId, diff --git a/services/api-server/src/simcore_service_api_server/api/routes/solvers.py b/services/api-server/src/simcore_service_api_server/api/routes/solvers.py index c162e359422..f6dee01f569 100644 --- a/services/api-server/src/simcore_service_api_server/api/routes/solvers.py +++ b/services/api-server/src/simcore_service_api_server/api/routes/solvers.py @@ -83,10 +83,10 @@ async def list_solvers( description=create_route_description( base="Lists all available solvers (paginated)", changelog=[ - FMSG_CHANGELOG_NEW_IN_VERSION.format("0.9-rc1"), + FMSG_CHANGELOG_NEW_IN_VERSION.format("0.10-rc1"), ], ), - include_in_schema=False, # TO BE RELEASED in 0.9 + include_in_schema=False, # TO BE RELEASED in 0.10-rc1 ) async def list_all_solvers_paginated( page_params: Annotated[PaginationParams, Depends()], @@ -246,10 +246,10 @@ async def list_solver_releases( description=create_route_description( base="Lists all releases of a give solver (paginated)", changelog=[ - FMSG_CHANGELOG_NEW_IN_VERSION.format("0.9-rc1"), + FMSG_CHANGELOG_NEW_IN_VERSION.format("0.10-rc1"), ], ), - include_in_schema=False, # TO BE RELEASED in 0.9 + include_in_schema=False, # TO BE RELEASED in 0.10-rc1 ) async def list_solver_releases_paginated( solver_key: SolverKeyId, diff --git a/services/api-server/src/simcore_service_api_server/api/routes/solvers_jobs_read.py b/services/api-server/src/simcore_service_api_server/api/routes/solvers_jobs_read.py index 7210b9b2c97..f46ea4d9892 100644 --- a/services/api-server/src/simcore_service_api_server/api/routes/solvers_jobs_read.py +++ b/services/api-server/src/simcore_service_api_server/api/routes/solvers_jobs_read.py @@ -130,10 +130,10 @@ description=create_route_description( base="List of all jobs created for any released solver (paginated)", changelog=[ - FMSG_CHANGELOG_NEW_IN_VERSION.format("0.9-rc1"), + FMSG_CHANGELOG_NEW_IN_VERSION.format("0.10-rc1"), ], ), - include_in_schema=False, # TO BE RELEASED in 0.9 + include_in_schema=False, # TO BE RELEASED in 0.10-rc1 ) async def list_all_solvers_jobs( page_params: Annotated[PaginationParams, Depends()], diff --git a/services/api-server/src/simcore_service_api_server/api/routes/studies_jobs.py b/services/api-server/src/simcore_service_api_server/api/routes/studies_jobs.py index c19845df2aa..11e8a22e4a7 100644 --- a/services/api-server/src/simcore_service_api_server/api/routes/studies_jobs.py +++ b/services/api-server/src/simcore_service_api_server/api/routes/studies_jobs.py @@ -52,17 +52,13 @@ from ..dependencies.authentication import get_current_user_id, get_product_name from ..dependencies.services import get_api_client, get_study_service from ..dependencies.webserver_http import AuthSession, get_webserver_session -from ..dependencies.webserver_rpc import ( - get_wb_api_rpc_client, -) +from ..dependencies.webserver_rpc import get_wb_api_rpc_client from ._constants import ( FMSG_CHANGELOG_CHANGED_IN_VERSION, FMSG_CHANGELOG_NEW_IN_VERSION, create_route_description, ) -from .solvers_jobs import ( - JOBS_STATUS_CODES, -) +from .solvers_jobs import JOBS_STATUS_CODES # pylint: disable=too-many-arguments @@ -87,10 +83,10 @@ def _compose_job_resource_name(study_key, job_id) -> str: description=create_route_description( base="List of all jobs created for a given study (paginated)", changelog=[ - FMSG_CHANGELOG_NEW_IN_VERSION.format("0.9-rc1"), + FMSG_CHANGELOG_NEW_IN_VERSION.format("0.10-rc1"), ], ), - include_in_schema=False, # TO BE RELEASED in 0.9 + include_in_schema=False, # TO BE RELEASED in 0.10-rc1 ) async def list_study_jobs( study_id: StudyID, @@ -217,10 +213,10 @@ async def create_study_job( description=create_route_description( base="Gets a jobs for a given study", changelog=[ - FMSG_CHANGELOG_NEW_IN_VERSION.format("0.9-rc1"), + FMSG_CHANGELOG_NEW_IN_VERSION.format("0.10-rc1"), ], ), - include_in_schema=False, # TO BE RELEASED in 0.9 + include_in_schema=False, # TO BE RELEASED in 0.10-rc1 ) async def get_study_job( study_id: StudyID, diff --git a/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_function_job_collections_controller_rpc.py b/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_function_job_collections_controller_rpc.py index 66935054b71..d784a9fad3b 100644 --- a/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_function_job_collections_controller_rpc.py +++ b/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_function_job_collections_controller_rpc.py @@ -14,8 +14,6 @@ ProjectFunctionJob, ) from models_library.functions import FunctionJobCollectionsListFilters - -# import simcore_service_webserver.functions._functions_controller_rpc as functions_rpc from models_library.functions_errors import ( FunctionJobCollectionReadAccessDeniedError, FunctionJobIDNotFoundError, diff --git a/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_function_jobs_controller_rpc.py b/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_function_jobs_controller_rpc.py index 6c9517077a1..f7b6b16e2df 100644 --- a/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_function_jobs_controller_rpc.py +++ b/services/web/server/tests/unit/with_dbs/04/functions_rpc/test_function_jobs_controller_rpc.py @@ -11,8 +11,6 @@ ProjectFunction, ProjectFunctionJob, ) - -# import simcore_service_webserver.functions._functions_controller_rpc as functions_rpc from models_library.functions_errors import ( FunctionJobIDNotFoundError, FunctionJobReadAccessDeniedError, From 428b2de77fdfd82980c308186f1750061d31a292 Mon Sep 17 00:00:00 2001 From: Werner Van Geit Date: Wed, 4 Jun 2025 12:53:41 +0200 Subject: [PATCH 8/8] =?UTF-8?q?services/webserver=20api=20version:=200.67.?= =?UTF-8?q?0=20=E2=86=92=200.68.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- services/web/server/VERSION | 2 +- services/web/server/setup.cfg | 2 +- .../server/src/simcore_service_webserver/api/v0/openapi.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/services/web/server/VERSION b/services/web/server/VERSION index 328185caaeb..4a46fb5b7a1 100644 --- a/services/web/server/VERSION +++ b/services/web/server/VERSION @@ -1 +1 @@ -0.67.0 +0.68.0 diff --git a/services/web/server/setup.cfg b/services/web/server/setup.cfg index 13e5b2c3d4e..8400824ccb5 100644 --- a/services/web/server/setup.cfg +++ b/services/web/server/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.67.0 +current_version = 0.68.0 commit = True message = services/webserver api version: {current_version} → {new_version} tag = False diff --git a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml index e65606826b8..8addd2a9eac 100644 --- a/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml +++ b/services/web/server/src/simcore_service_webserver/api/v0/openapi.yaml @@ -2,7 +2,7 @@ openapi: 3.1.0 info: title: simcore-service-webserver description: Main service with an interface (http-API & websockets) to the web front-end - version: 0.67.0 + version: 0.68.0 servers: - url: '' description: webserver