From 156a19a03af104d06e8d62675b3920ad1bffcc4d Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Fri, 14 Feb 2025 14:26:03 +0100 Subject: [PATCH 01/63] factor out tasks part in storage rpc api --- .../api/rpc/_data_export.py | 36 +--------------- .../simcore_service_storage/api/rpc/_tasks.py | 41 +++++++++++++++++++ .../simcore_service_storage/api/rpc/routes.py | 4 +- 3 files changed, 44 insertions(+), 37 deletions(-) create mode 100644 services/storage/src/simcore_service_storage/api/rpc/_tasks.py diff --git a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py index 08520687803..496929adcca 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py @@ -1,15 +1,8 @@ -from datetime import datetime from uuid import uuid4 from fastapi import FastAPI -from models_library.api_schemas_rpc_data_export.tasks import ( - TaskRpcGet, - TaskRpcId, - TaskRpcResult, - TaskRpcStatus, -) +from models_library.api_schemas_rpc_data_export.tasks import TaskRpcGet from models_library.api_schemas_storage.data_export_tasks import ( - DataExportTaskAbortOutput, DataExportTaskStartInput, ) from servicelib.rabbitmq import RPCRouter @@ -26,30 +19,3 @@ async def start_data_export( task_id=uuid4(), task_name=", ".join(str(p) for p in paths.paths), ) - - -@router.expose() -async def abort_data_export( - app: FastAPI, task_id: TaskRpcId -) -> DataExportTaskAbortOutput: - assert app # nosec - return DataExportTaskAbortOutput(result=True, task_id=task_id) - - -@router.expose() -async def get_data_export_status(app: FastAPI, task_id: TaskRpcId) -> TaskRpcStatus: - assert app # nosec - return TaskRpcStatus( - task_id=task_id, - task_progress=0.5, - done=False, - started=datetime.now(), - stopped=None, - ) - - -@router.expose() -async def get_data_export_result(app: FastAPI, task_id: TaskRpcId) -> TaskRpcResult: - assert app # nosec - assert task_id # nosec - return TaskRpcResult(result="Here's your result.", error=None) diff --git a/services/storage/src/simcore_service_storage/api/rpc/_tasks.py b/services/storage/src/simcore_service_storage/api/rpc/_tasks.py new file mode 100644 index 00000000000..62d16f63145 --- /dev/null +++ b/services/storage/src/simcore_service_storage/api/rpc/_tasks.py @@ -0,0 +1,41 @@ +from datetime import datetime + +from fastapi import FastAPI +from models_library.api_schemas_rpc_data_export.tasks import ( + TaskRpcId, + TaskRpcResult, + TaskRpcStatus, +) +from models_library.api_schemas_storage.data_export_tasks import ( + DataExportTaskAbortOutput, +) +from servicelib.rabbitmq import RPCRouter + +router = RPCRouter() + + +@router.expose() +async def abort_data_export( + app: FastAPI, task_id: TaskRpcId +) -> DataExportTaskAbortOutput: + assert app # nosec + return DataExportTaskAbortOutput(result=True, task_id=task_id) + + +@router.expose() +async def get_data_export_status(app: FastAPI, task_id: TaskRpcId) -> TaskRpcStatus: + assert app # nosec + return TaskRpcStatus( + task_id=task_id, + task_progress=0.5, + done=False, + started=datetime.now(), + stopped=None, + ) + + +@router.expose() +async def get_data_export_result(app: FastAPI, task_id: TaskRpcId) -> TaskRpcResult: + assert app # nosec + assert task_id # nosec + return TaskRpcResult(result="Here's your result.", error=None) diff --git a/services/storage/src/simcore_service_storage/api/rpc/routes.py b/services/storage/src/simcore_service_storage/api/rpc/routes.py index 4a39c8d9288..218b97d3c94 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/routes.py +++ b/services/storage/src/simcore_service_storage/api/rpc/routes.py @@ -6,12 +6,12 @@ from servicelib.rabbitmq import RPCRouter from ...modules.rabbitmq import get_rabbitmq_rpc_server -from . import _data_export +from . import _data_export, _tasks _logger = logging.getLogger(__name__) -ROUTERS: list[RPCRouter] = [_data_export.router] +ROUTERS: list[RPCRouter] = [_data_export.router, _tasks.router] def setup_rpc_api_routes(app: FastAPI) -> None: From 64c137d69c865838f86467a844c1525250c01819 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Fri, 14 Feb 2025 14:38:02 +0100 Subject: [PATCH 02/63] use 'async_jobs' terminology and refactor rpc client for reusability --- .../rpc_interfaces/async_jobs/__init__.py | 0 .../rpc_interfaces/async_jobs/async_jobs.py | 58 +++++++++++++++++++ .../rpc_interfaces/storage/data_export.py | 47 +-------------- .../storage/tests/unit/test_data_export.py | 7 ++- 4 files changed, 63 insertions(+), 49 deletions(-) create mode 100644 packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/__init__.py create mode 100644 packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/__init__.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py new file mode 100644 index 00000000000..58ab99754b9 --- /dev/null +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py @@ -0,0 +1,58 @@ +from typing import Final + +from models_library.api_schemas_rpc_data_export.tasks import ( + TaskRpcId, + TaskRpcResult, + TaskRpcStatus, +) +from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE +from models_library.api_schemas_storage.data_export_tasks import ( + DataExportTaskAbortOutput, +) +from models_library.rabbitmq_basic_types import RPCMethodName +from pydantic import NonNegativeInt, TypeAdapter + +from ... import RabbitMQRPCClient + +_DEFAULT_TIMEOUT_S: Final[NonNegativeInt] = 30 + +_RPC_METHOD_NAME_ADAPTER = TypeAdapter(RPCMethodName) + + +async def abort( + rabbitmq_rpc_client: RabbitMQRPCClient, *, task_id: TaskRpcId +) -> DataExportTaskAbortOutput: + result = await rabbitmq_rpc_client.request( + STORAGE_RPC_NAMESPACE, + _RPC_METHOD_NAME_ADAPTER.validate_python("abort_data_export"), + task_id=task_id, + timeout_s=_DEFAULT_TIMEOUT_S, + ) + assert isinstance(result, DataExportTaskAbortOutput) + return result + + +async def get_status( + rabbitmq_rpc_client: RabbitMQRPCClient, *, task_id: TaskRpcId +) -> TaskRpcStatus: + result = await rabbitmq_rpc_client.request( + STORAGE_RPC_NAMESPACE, + _RPC_METHOD_NAME_ADAPTER.validate_python("get_data_export_status"), + task_id=task_id, + timeout_s=_DEFAULT_TIMEOUT_S, + ) + assert isinstance(result, TaskRpcStatus) + return result + + +async def get_result( + rabbitmq_rpc_client: RabbitMQRPCClient, *, task_id: TaskRpcId +) -> TaskRpcResult: + result = await rabbitmq_rpc_client.request( + STORAGE_RPC_NAMESPACE, + _RPC_METHOD_NAME_ADAPTER.validate_python("get_data_export_result"), + task_id=task_id, + timeout_s=_DEFAULT_TIMEOUT_S, + ) + assert isinstance(result, TaskRpcResult) + return result diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py index 8e71ae1ec68..4ea26128f79 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py @@ -1,14 +1,8 @@ from typing import Final -from models_library.api_schemas_rpc_data_export.tasks import ( - TaskRpcGet, - TaskRpcId, - TaskRpcResult, - TaskRpcStatus, -) +from models_library.api_schemas_rpc_data_export.tasks import TaskRpcGet from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE from models_library.api_schemas_storage.data_export_tasks import ( - DataExportTaskAbortOutput, DataExportTaskStartInput, ) from models_library.rabbitmq_basic_types import RPCMethodName @@ -32,42 +26,3 @@ async def start_data_export( ) assert isinstance(result, TaskRpcGet) return result - - -async def abort_data_export( - rabbitmq_rpc_client: RabbitMQRPCClient, *, task_id: TaskRpcId -) -> DataExportTaskAbortOutput: - result = await rabbitmq_rpc_client.request( - STORAGE_RPC_NAMESPACE, - _RPC_METHOD_NAME_ADAPTER.validate_python("abort_data_export"), - task_id=task_id, - timeout_s=_DEFAULT_TIMEOUT_S, - ) - assert isinstance(result, DataExportTaskAbortOutput) - return result - - -async def get_data_export_status( - rabbitmq_rpc_client: RabbitMQRPCClient, *, task_id: TaskRpcId -) -> TaskRpcStatus: - result = await rabbitmq_rpc_client.request( - STORAGE_RPC_NAMESPACE, - _RPC_METHOD_NAME_ADAPTER.validate_python("get_data_export_status"), - task_id=task_id, - timeout_s=_DEFAULT_TIMEOUT_S, - ) - assert isinstance(result, TaskRpcStatus) - return result - - -async def get_data_export_result( - rabbitmq_rpc_client: RabbitMQRPCClient, *, task_id: TaskRpcId -) -> TaskRpcResult: - result = await rabbitmq_rpc_client.request( - STORAGE_RPC_NAMESPACE, - _RPC_METHOD_NAME_ADAPTER.validate_python("get_data_export_result"), - task_id=task_id, - timeout_s=_DEFAULT_TIMEOUT_S, - ) - assert isinstance(result, TaskRpcResult) - return result diff --git a/services/storage/tests/unit/test_data_export.py b/services/storage/tests/unit/test_data_export.py index 1d350753c32..751bd9c49cf 100644 --- a/services/storage/tests/unit/test_data_export.py +++ b/services/storage/tests/unit/test_data_export.py @@ -20,6 +20,7 @@ from pytest_simcore.helpers.monkeypatch_envs import setenvs_from_dict from pytest_simcore.helpers.typing_env import EnvVarsDict from servicelib.rabbitmq import RabbitMQRPCClient +from servicelib.rabbitmq.rpc_interfaces.async_jobs import async_jobs from servicelib.rabbitmq.rpc_interfaces.storage import data_export from settings_library.rabbit import RabbitSettings from simcore_service_storage.core.settings import ApplicationSettings @@ -82,19 +83,19 @@ async def test_start_data_export(rpc_client: RabbitMQRPCClient, faker: Faker): async def test_abort_data_export(rpc_client: RabbitMQRPCClient, faker: Faker): _task_id = TaskRpcId(faker.uuid4()) - result = await data_export.abort_data_export(rpc_client, task_id=_task_id) + result = await async_jobs.abort(rpc_client, task_id=_task_id) assert isinstance(result, DataExportTaskAbortOutput) assert result.task_id == _task_id async def test_get_data_export_status(rpc_client: RabbitMQRPCClient, faker: Faker): _task_id = TaskRpcId(faker.uuid4()) - result = await data_export.get_data_export_status(rpc_client, task_id=_task_id) + result = await async_jobs.get_status(rpc_client, task_id=_task_id) assert isinstance(result, TaskRpcStatus) assert result.task_id == _task_id async def test_get_data_export_result(rpc_client: RabbitMQRPCClient, faker: Faker): _task_id = TaskRpcId(faker.uuid4()) - result = await data_export.get_data_export_result(rpc_client, task_id=_task_id) + result = await async_jobs.get_result(rpc_client, task_id=_task_id) assert isinstance(result, TaskRpcResult) From f6e73e3458873253cbadeedfb6580264bc7c920e Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Fri, 14 Feb 2025 14:42:42 +0100 Subject: [PATCH 03/63] further refactoring --- .../rabbitmq/rpc_interfaces/async_jobs/async_jobs.py | 6 +++--- .../api/rpc/{_tasks.py => _async_jobs.py} | 8 +++----- .../storage/src/simcore_service_storage/api/rpc/routes.py | 4 ++-- 3 files changed, 8 insertions(+), 10 deletions(-) rename services/storage/src/simcore_service_storage/api/rpc/{_tasks.py => _async_jobs.py} (74%) diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py index 58ab99754b9..6c5b1fe72a9 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py @@ -24,7 +24,7 @@ async def abort( ) -> DataExportTaskAbortOutput: result = await rabbitmq_rpc_client.request( STORAGE_RPC_NAMESPACE, - _RPC_METHOD_NAME_ADAPTER.validate_python("abort_data_export"), + _RPC_METHOD_NAME_ADAPTER.validate_python("abort"), task_id=task_id, timeout_s=_DEFAULT_TIMEOUT_S, ) @@ -37,7 +37,7 @@ async def get_status( ) -> TaskRpcStatus: result = await rabbitmq_rpc_client.request( STORAGE_RPC_NAMESPACE, - _RPC_METHOD_NAME_ADAPTER.validate_python("get_data_export_status"), + _RPC_METHOD_NAME_ADAPTER.validate_python("get_status"), task_id=task_id, timeout_s=_DEFAULT_TIMEOUT_S, ) @@ -50,7 +50,7 @@ async def get_result( ) -> TaskRpcResult: result = await rabbitmq_rpc_client.request( STORAGE_RPC_NAMESPACE, - _RPC_METHOD_NAME_ADAPTER.validate_python("get_data_export_result"), + _RPC_METHOD_NAME_ADAPTER.validate_python("get_result"), task_id=task_id, timeout_s=_DEFAULT_TIMEOUT_S, ) diff --git a/services/storage/src/simcore_service_storage/api/rpc/_tasks.py b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py similarity index 74% rename from services/storage/src/simcore_service_storage/api/rpc/_tasks.py rename to services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py index 62d16f63145..03a3894043f 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_tasks.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py @@ -15,15 +15,13 @@ @router.expose() -async def abort_data_export( - app: FastAPI, task_id: TaskRpcId -) -> DataExportTaskAbortOutput: +async def abort(app: FastAPI, task_id: TaskRpcId) -> DataExportTaskAbortOutput: assert app # nosec return DataExportTaskAbortOutput(result=True, task_id=task_id) @router.expose() -async def get_data_export_status(app: FastAPI, task_id: TaskRpcId) -> TaskRpcStatus: +async def get_status(app: FastAPI, task_id: TaskRpcId) -> TaskRpcStatus: assert app # nosec return TaskRpcStatus( task_id=task_id, @@ -35,7 +33,7 @@ async def get_data_export_status(app: FastAPI, task_id: TaskRpcId) -> TaskRpcSta @router.expose() -async def get_data_export_result(app: FastAPI, task_id: TaskRpcId) -> TaskRpcResult: +async def get_result(app: FastAPI, task_id: TaskRpcId) -> TaskRpcResult: assert app # nosec assert task_id # nosec return TaskRpcResult(result="Here's your result.", error=None) diff --git a/services/storage/src/simcore_service_storage/api/rpc/routes.py b/services/storage/src/simcore_service_storage/api/rpc/routes.py index 218b97d3c94..812ce296adf 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/routes.py +++ b/services/storage/src/simcore_service_storage/api/rpc/routes.py @@ -6,12 +6,12 @@ from servicelib.rabbitmq import RPCRouter from ...modules.rabbitmq import get_rabbitmq_rpc_server -from . import _data_export, _tasks +from . import _async_jobs, _data_export _logger = logging.getLogger(__name__) -ROUTERS: list[RPCRouter] = [_data_export.router, _tasks.router] +ROUTERS: list[RPCRouter] = [_data_export.router, _async_jobs.router] def setup_rpc_api_routes(app: FastAPI) -> None: From 50fa599966b9328267333970be5eb679d705d277 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Fri, 14 Feb 2025 14:53:08 +0100 Subject: [PATCH 04/63] make async job rpc client more reusable --- .../rpc_interfaces/async_jobs/async_jobs.py | 24 ++++++++++++------- .../storage/tests/unit/test_data_export.py | 13 +++++++--- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py index 6c5b1fe72a9..8ce0120d06c 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py @@ -5,11 +5,10 @@ TaskRpcResult, TaskRpcStatus, ) -from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE from models_library.api_schemas_storage.data_export_tasks import ( DataExportTaskAbortOutput, ) -from models_library.rabbitmq_basic_types import RPCMethodName +from models_library.rabbitmq_basic_types import RPCMethodName, RPCNamespace from pydantic import NonNegativeInt, TypeAdapter from ... import RabbitMQRPCClient @@ -20,10 +19,13 @@ async def abort( - rabbitmq_rpc_client: RabbitMQRPCClient, *, task_id: TaskRpcId + rabbitmq_rpc_client: RabbitMQRPCClient, + *, + rpc_namespace: RPCNamespace, + task_id: TaskRpcId ) -> DataExportTaskAbortOutput: result = await rabbitmq_rpc_client.request( - STORAGE_RPC_NAMESPACE, + rpc_namespace, _RPC_METHOD_NAME_ADAPTER.validate_python("abort"), task_id=task_id, timeout_s=_DEFAULT_TIMEOUT_S, @@ -33,10 +35,13 @@ async def abort( async def get_status( - rabbitmq_rpc_client: RabbitMQRPCClient, *, task_id: TaskRpcId + rabbitmq_rpc_client: RabbitMQRPCClient, + *, + rpc_namespace: RPCNamespace, + task_id: TaskRpcId ) -> TaskRpcStatus: result = await rabbitmq_rpc_client.request( - STORAGE_RPC_NAMESPACE, + rpc_namespace, _RPC_METHOD_NAME_ADAPTER.validate_python("get_status"), task_id=task_id, timeout_s=_DEFAULT_TIMEOUT_S, @@ -46,10 +51,13 @@ async def get_status( async def get_result( - rabbitmq_rpc_client: RabbitMQRPCClient, *, task_id: TaskRpcId + rabbitmq_rpc_client: RabbitMQRPCClient, + *, + rpc_namespace: RPCNamespace, + task_id: TaskRpcId ) -> TaskRpcResult: result = await rabbitmq_rpc_client.request( - STORAGE_RPC_NAMESPACE, + rpc_namespace, _RPC_METHOD_NAME_ADAPTER.validate_python("get_result"), task_id=task_id, timeout_s=_DEFAULT_TIMEOUT_S, diff --git a/services/storage/tests/unit/test_data_export.py b/services/storage/tests/unit/test_data_export.py index 751bd9c49cf..b1839daa59b 100644 --- a/services/storage/tests/unit/test_data_export.py +++ b/services/storage/tests/unit/test_data_export.py @@ -12,6 +12,7 @@ TaskRpcResult, TaskRpcStatus, ) +from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE from models_library.api_schemas_storage.data_export_tasks import ( DataExportTaskAbortOutput, DataExportTaskStartInput, @@ -83,19 +84,25 @@ async def test_start_data_export(rpc_client: RabbitMQRPCClient, faker: Faker): async def test_abort_data_export(rpc_client: RabbitMQRPCClient, faker: Faker): _task_id = TaskRpcId(faker.uuid4()) - result = await async_jobs.abort(rpc_client, task_id=_task_id) + result = await async_jobs.abort( + rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, task_id=_task_id + ) assert isinstance(result, DataExportTaskAbortOutput) assert result.task_id == _task_id async def test_get_data_export_status(rpc_client: RabbitMQRPCClient, faker: Faker): _task_id = TaskRpcId(faker.uuid4()) - result = await async_jobs.get_status(rpc_client, task_id=_task_id) + result = await async_jobs.get_status( + rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, task_id=_task_id + ) assert isinstance(result, TaskRpcStatus) assert result.task_id == _task_id async def test_get_data_export_result(rpc_client: RabbitMQRPCClient, faker: Faker): _task_id = TaskRpcId(faker.uuid4()) - result = await async_jobs.get_result(rpc_client, task_id=_task_id) + result = await async_jobs.get_result( + rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, task_id=_task_id + ) assert isinstance(result, TaskRpcResult) From 1ce71580d561e64bbde318320e7eee6bef249cfc Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Sat, 15 Feb 2025 08:16:42 +0100 Subject: [PATCH 05/63] tasks -> async jobs --- .../api_schemas_rpc_data_export/tasks.py | 12 +++++------ .../api_schemas_storage/data_export_tasks.py | 4 ++-- .../rpc_interfaces/async_jobs/async_jobs.py | 20 +++++++++---------- .../rpc_interfaces/storage/data_export.py | 6 +++--- .../api/rpc/_async_jobs.py | 16 +++++++-------- .../api/rpc/_data_export.py | 6 +++--- .../storage/tests/unit/test_data_export.py | 20 +++++++++---------- 7 files changed, 42 insertions(+), 42 deletions(-) diff --git a/packages/models-library/src/models_library/api_schemas_rpc_data_export/tasks.py b/packages/models-library/src/models_library/api_schemas_rpc_data_export/tasks.py index 65787850d29..14fb658780e 100644 --- a/packages/models-library/src/models_library/api_schemas_rpc_data_export/tasks.py +++ b/packages/models-library/src/models_library/api_schemas_rpc_data_export/tasks.py @@ -5,11 +5,11 @@ from pydantic import BaseModel, Field, PositiveFloat, model_validator from typing_extensions import Self -TaskRpcId: TypeAlias = UUID +AsyncJobRpcId: TypeAlias = UUID -class TaskRpcStatus(BaseModel): - task_id: TaskRpcId +class AsyncJobRpcStatus(BaseModel): + task_id: AsyncJobRpcId task_progress: PositiveFloat = Field(..., ge=0.0, le=1.0) done: bool started: datetime @@ -26,11 +26,11 @@ def _check_consistency(self) -> Self: return self -class TaskRpcResult(BaseModel): +class AsyncJobRpcResult(BaseModel): result: Any | None error: Any | None -class TaskRpcGet(BaseModel): - task_id: TaskRpcId +class AsyncJobRpcGet(BaseModel): + task_id: AsyncJobRpcId task_name: str diff --git a/packages/models-library/src/models_library/api_schemas_storage/data_export_tasks.py b/packages/models-library/src/models_library/api_schemas_storage/data_export_tasks.py index 242ea2eb42e..6f25e23c163 100644 --- a/packages/models-library/src/models_library/api_schemas_storage/data_export_tasks.py +++ b/packages/models-library/src/models_library/api_schemas_storage/data_export_tasks.py @@ -1,7 +1,7 @@ # pylint: disable=R6301 from pathlib import Path -from models_library.api_schemas_rpc_data_export.tasks import TaskRpcId +from models_library.api_schemas_rpc_data_export.tasks import AsyncJobRpcId from pydantic import BaseModel, Field @@ -11,4 +11,4 @@ class DataExportTaskStartInput(BaseModel): class DataExportTaskAbortOutput(BaseModel): result: bool - task_id: TaskRpcId + task_id: AsyncJobRpcId diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py index 8ce0120d06c..c6ef60f6906 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py @@ -1,9 +1,9 @@ from typing import Final from models_library.api_schemas_rpc_data_export.tasks import ( - TaskRpcId, - TaskRpcResult, - TaskRpcStatus, + AsyncJobRpcId, + AsyncJobRpcResult, + AsyncJobRpcStatus, ) from models_library.api_schemas_storage.data_export_tasks import ( DataExportTaskAbortOutput, @@ -22,7 +22,7 @@ async def abort( rabbitmq_rpc_client: RabbitMQRPCClient, *, rpc_namespace: RPCNamespace, - task_id: TaskRpcId + task_id: AsyncJobRpcId ) -> DataExportTaskAbortOutput: result = await rabbitmq_rpc_client.request( rpc_namespace, @@ -38,15 +38,15 @@ async def get_status( rabbitmq_rpc_client: RabbitMQRPCClient, *, rpc_namespace: RPCNamespace, - task_id: TaskRpcId -) -> TaskRpcStatus: + task_id: AsyncJobRpcId +) -> AsyncJobRpcStatus: result = await rabbitmq_rpc_client.request( rpc_namespace, _RPC_METHOD_NAME_ADAPTER.validate_python("get_status"), task_id=task_id, timeout_s=_DEFAULT_TIMEOUT_S, ) - assert isinstance(result, TaskRpcStatus) + assert isinstance(result, AsyncJobRpcStatus) return result @@ -54,13 +54,13 @@ async def get_result( rabbitmq_rpc_client: RabbitMQRPCClient, *, rpc_namespace: RPCNamespace, - task_id: TaskRpcId -) -> TaskRpcResult: + task_id: AsyncJobRpcId +) -> AsyncJobRpcResult: result = await rabbitmq_rpc_client.request( rpc_namespace, _RPC_METHOD_NAME_ADAPTER.validate_python("get_result"), task_id=task_id, timeout_s=_DEFAULT_TIMEOUT_S, ) - assert isinstance(result, TaskRpcResult) + assert isinstance(result, AsyncJobRpcResult) return result diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py index 4ea26128f79..adf841e18ea 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py @@ -1,6 +1,6 @@ from typing import Final -from models_library.api_schemas_rpc_data_export.tasks import TaskRpcGet +from models_library.api_schemas_rpc_data_export.tasks import AsyncJobRpcGet from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE from models_library.api_schemas_storage.data_export_tasks import ( DataExportTaskStartInput, @@ -17,12 +17,12 @@ async def start_data_export( rabbitmq_rpc_client: RabbitMQRPCClient, *, paths: DataExportTaskStartInput -) -> TaskRpcGet: +) -> AsyncJobRpcGet: result = await rabbitmq_rpc_client.request( STORAGE_RPC_NAMESPACE, _RPC_METHOD_NAME_ADAPTER.validate_python("start_data_export"), paths=paths, timeout_s=_DEFAULT_TIMEOUT_S, ) - assert isinstance(result, TaskRpcGet) + assert isinstance(result, AsyncJobRpcGet) return result diff --git a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py index 03a3894043f..eaea79d96ba 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py @@ -2,9 +2,9 @@ from fastapi import FastAPI from models_library.api_schemas_rpc_data_export.tasks import ( - TaskRpcId, - TaskRpcResult, - TaskRpcStatus, + AsyncJobRpcId, + AsyncJobRpcResult, + AsyncJobRpcStatus, ) from models_library.api_schemas_storage.data_export_tasks import ( DataExportTaskAbortOutput, @@ -15,15 +15,15 @@ @router.expose() -async def abort(app: FastAPI, task_id: TaskRpcId) -> DataExportTaskAbortOutput: +async def abort(app: FastAPI, task_id: AsyncJobRpcId) -> DataExportTaskAbortOutput: assert app # nosec return DataExportTaskAbortOutput(result=True, task_id=task_id) @router.expose() -async def get_status(app: FastAPI, task_id: TaskRpcId) -> TaskRpcStatus: +async def get_status(app: FastAPI, task_id: AsyncJobRpcId) -> AsyncJobRpcStatus: assert app # nosec - return TaskRpcStatus( + return AsyncJobRpcStatus( task_id=task_id, task_progress=0.5, done=False, @@ -33,7 +33,7 @@ async def get_status(app: FastAPI, task_id: TaskRpcId) -> TaskRpcStatus: @router.expose() -async def get_result(app: FastAPI, task_id: TaskRpcId) -> TaskRpcResult: +async def get_result(app: FastAPI, task_id: AsyncJobRpcId) -> AsyncJobRpcResult: assert app # nosec assert task_id # nosec - return TaskRpcResult(result="Here's your result.", error=None) + return AsyncJobRpcResult(result="Here's your result.", error=None) diff --git a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py index 496929adcca..7ea8b8120db 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py @@ -1,7 +1,7 @@ from uuid import uuid4 from fastapi import FastAPI -from models_library.api_schemas_rpc_data_export.tasks import TaskRpcGet +from models_library.api_schemas_rpc_data_export.tasks import AsyncJobRpcGet from models_library.api_schemas_storage.data_export_tasks import ( DataExportTaskStartInput, ) @@ -13,9 +13,9 @@ @router.expose() async def start_data_export( app: FastAPI, paths: DataExportTaskStartInput -) -> TaskRpcGet: +) -> AsyncJobRpcGet: assert app # nosec - return TaskRpcGet( + return AsyncJobRpcGet( task_id=uuid4(), task_name=", ".join(str(p) for p in paths.paths), ) diff --git a/services/storage/tests/unit/test_data_export.py b/services/storage/tests/unit/test_data_export.py index b1839daa59b..3261939b118 100644 --- a/services/storage/tests/unit/test_data_export.py +++ b/services/storage/tests/unit/test_data_export.py @@ -7,10 +7,10 @@ from faker import Faker from fastapi import FastAPI from models_library.api_schemas_rpc_data_export.tasks import ( - TaskRpcGet, - TaskRpcId, - TaskRpcResult, - TaskRpcStatus, + AsyncJobRpcGet, + AsyncJobRpcId, + AsyncJobRpcResult, + AsyncJobRpcStatus, ) from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE from models_library.api_schemas_storage.data_export_tasks import ( @@ -79,11 +79,11 @@ async def test_start_data_export(rpc_client: RabbitMQRPCClient, faker: Faker): result = await data_export.start_data_export( rpc_client, paths=DataExportTaskStartInput(paths=[Path(faker.file_path())]) ) - assert isinstance(result, TaskRpcGet) + assert isinstance(result, AsyncJobRpcGet) async def test_abort_data_export(rpc_client: RabbitMQRPCClient, faker: Faker): - _task_id = TaskRpcId(faker.uuid4()) + _task_id = AsyncJobRpcId(faker.uuid4()) result = await async_jobs.abort( rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, task_id=_task_id ) @@ -92,17 +92,17 @@ async def test_abort_data_export(rpc_client: RabbitMQRPCClient, faker: Faker): async def test_get_data_export_status(rpc_client: RabbitMQRPCClient, faker: Faker): - _task_id = TaskRpcId(faker.uuid4()) + _task_id = AsyncJobRpcId(faker.uuid4()) result = await async_jobs.get_status( rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, task_id=_task_id ) - assert isinstance(result, TaskRpcStatus) + assert isinstance(result, AsyncJobRpcStatus) assert result.task_id == _task_id async def test_get_data_export_result(rpc_client: RabbitMQRPCClient, faker: Faker): - _task_id = TaskRpcId(faker.uuid4()) + _task_id = AsyncJobRpcId(faker.uuid4()) result = await async_jobs.get_result( rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, task_id=_task_id ) - assert isinstance(result, TaskRpcResult) + assert isinstance(result, AsyncJobRpcResult) From 1229e9290b4b279e70f4ec55c550204e4cb34df3 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Sat, 15 Feb 2025 08:19:03 +0100 Subject: [PATCH 06/63] tasks -> async_job --- .../api_schemas_rpc_data_export/{tasks.py => async_jobs.py} | 0 .../src/models_library/api_schemas_storage/data_export_tasks.py | 2 +- .../servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py | 2 +- .../servicelib/rabbitmq/rpc_interfaces/storage/data_export.py | 2 +- .../storage/src/simcore_service_storage/api/rpc/_async_jobs.py | 2 +- .../storage/src/simcore_service_storage/api/rpc/_data_export.py | 2 +- services/storage/tests/unit/test_data_export.py | 2 +- 7 files changed, 6 insertions(+), 6 deletions(-) rename packages/models-library/src/models_library/api_schemas_rpc_data_export/{tasks.py => async_jobs.py} (100%) diff --git a/packages/models-library/src/models_library/api_schemas_rpc_data_export/tasks.py b/packages/models-library/src/models_library/api_schemas_rpc_data_export/async_jobs.py similarity index 100% rename from packages/models-library/src/models_library/api_schemas_rpc_data_export/tasks.py rename to packages/models-library/src/models_library/api_schemas_rpc_data_export/async_jobs.py diff --git a/packages/models-library/src/models_library/api_schemas_storage/data_export_tasks.py b/packages/models-library/src/models_library/api_schemas_storage/data_export_tasks.py index 6f25e23c163..63c39888f4e 100644 --- a/packages/models-library/src/models_library/api_schemas_storage/data_export_tasks.py +++ b/packages/models-library/src/models_library/api_schemas_storage/data_export_tasks.py @@ -1,7 +1,7 @@ # pylint: disable=R6301 from pathlib import Path -from models_library.api_schemas_rpc_data_export.tasks import AsyncJobRpcId +from models_library.api_schemas_rpc_data_export.async_jobs import AsyncJobRpcId from pydantic import BaseModel, Field diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py index c6ef60f6906..1d2cd82d4af 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py @@ -1,6 +1,6 @@ from typing import Final -from models_library.api_schemas_rpc_data_export.tasks import ( +from models_library.api_schemas_rpc_data_export.async_jobs import ( AsyncJobRpcId, AsyncJobRpcResult, AsyncJobRpcStatus, diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py index adf841e18ea..301640b1191 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py @@ -1,6 +1,6 @@ from typing import Final -from models_library.api_schemas_rpc_data_export.tasks import AsyncJobRpcGet +from models_library.api_schemas_rpc_data_export.async_jobs import AsyncJobRpcGet from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE from models_library.api_schemas_storage.data_export_tasks import ( DataExportTaskStartInput, diff --git a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py index eaea79d96ba..601563dac6f 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py @@ -1,7 +1,7 @@ from datetime import datetime from fastapi import FastAPI -from models_library.api_schemas_rpc_data_export.tasks import ( +from models_library.api_schemas_rpc_data_export.async_jobs import ( AsyncJobRpcId, AsyncJobRpcResult, AsyncJobRpcStatus, diff --git a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py index 7ea8b8120db..30abb07e04c 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py @@ -1,7 +1,7 @@ from uuid import uuid4 from fastapi import FastAPI -from models_library.api_schemas_rpc_data_export.tasks import AsyncJobRpcGet +from models_library.api_schemas_rpc_data_export.async_jobs import AsyncJobRpcGet from models_library.api_schemas_storage.data_export_tasks import ( DataExportTaskStartInput, ) diff --git a/services/storage/tests/unit/test_data_export.py b/services/storage/tests/unit/test_data_export.py index 3261939b118..c43e94bf876 100644 --- a/services/storage/tests/unit/test_data_export.py +++ b/services/storage/tests/unit/test_data_export.py @@ -6,7 +6,7 @@ import pytest from faker import Faker from fastapi import FastAPI -from models_library.api_schemas_rpc_data_export.tasks import ( +from models_library.api_schemas_rpc_data_export.async_jobs import ( AsyncJobRpcGet, AsyncJobRpcId, AsyncJobRpcResult, From ce064ce1bcb61aaed69753ac46645fdd33b9d373 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Sat, 15 Feb 2025 08:36:42 +0100 Subject: [PATCH 07/63] start adding data-export endpoints in webserver --- .../src/models_library/storage_schemas.py | 24 ++++++++++ .../storage/_handlers.py | 48 ++++++++++++++----- 2 files changed, 60 insertions(+), 12 deletions(-) diff --git a/packages/models-library/src/models_library/storage_schemas.py b/packages/models-library/src/models_library/storage_schemas.py index 2a9ffc6acc3..0f90566cbdd 100644 --- a/packages/models-library/src/models_library/storage_schemas.py +++ b/packages/models-library/src/models_library/storage_schemas.py @@ -8,9 +8,16 @@ from datetime import datetime from enum import Enum + +# /data-export +from pathlib import Path from typing import Annotated, Any, Literal, Self, TypeAlias from uuid import UUID +from models_library.api_schemas_rpc_data_export.async_jobs import AsyncJobRpcGet +from models_library.api_schemas_storage.data_export_tasks import ( + DataExportTaskStartInput, +) from models_library.projects import ProjectID from models_library.users import UserID from pydantic import ( @@ -368,3 +375,20 @@ def ensure_consistent_entries(self: Self) -> Self: class SoftCopyBody(BaseModel): link_id: SimcoreS3FileID + + +class DataExportPost(BaseModel): + paths: list[Path] + + def to_storage_model(self) -> DataExportTaskStartInput: + return DataExportTaskStartInput(paths=self.paths) + + +class AsyncJobGet(BaseModel): + task_id: UUID + + @classmethod + def from_async_job_rpc_status( + cls, async_job_rpc_get: AsyncJobRpcGet + ) -> "AsyncJobGet": + return AsyncJobGet(task_id=async_job_rpc_get.task_id) diff --git a/services/web/server/src/simcore_service_webserver/storage/_handlers.py b/services/web/server/src/simcore_service_webserver/storage/_handlers.py index cca952ae562..4a5a7ce23ca 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage/_handlers.py @@ -11,6 +11,7 @@ from aiohttp import ClientTimeout, web from models_library.projects_nodes_io import LocationID from models_library.storage_schemas import ( + DataExportPost, FileUploadCompleteResponse, FileUploadCompletionBody, FileUploadSchema, @@ -27,8 +28,10 @@ ) from servicelib.aiohttp.rest_responses import create_data_response from servicelib.common_headers import X_FORWARDED_PROTO +from servicelib.rabbitmq.rpc_interfaces.storage.data_export import start_data_export from servicelib.request_keys import RQT_USERID_KEY from servicelib.rest_responses import unwrap_envelope +from simcore_service_webserver.rabbitmq import get_rabbitmq_rpc_client from yarl import URL from .._meta import API_VTAG @@ -121,10 +124,11 @@ async def _forward_request_to_storage( # --------------------------------------------------------------------- routes = web.RouteTableDef() -_path_prefix = f"/{API_VTAG}/storage/locations" +_storage_prefix = f"/{API_VTAG}/storage" +_storage_locations_prefix = f"/{_storage_prefix}/locations" -@routes.get(_path_prefix, name="list_storage_locations") +@routes.get(_storage_locations_prefix, name="list_storage_locations") @login_required @permission_required("storage.files.*") async def list_storage_locations(request: web.Request) -> web.Response: @@ -132,7 +136,9 @@ async def list_storage_locations(request: web.Request) -> web.Response: return create_data_response(payload, status=resp_status) -@routes.get(_path_prefix + "/{location_id}/datasets", name="list_datasets_metadata") +@routes.get( + _storage_locations_prefix + "/{location_id}/datasets", name="list_datasets_metadata" +) @login_required @permission_required("storage.files.*") async def list_datasets_metadata(request: web.Request) -> web.Response: @@ -146,7 +152,7 @@ class _PathParams(BaseModel): @routes.get( - _path_prefix + "/{location_id}/files/metadata", + _storage_locations_prefix + "/{location_id}/files/metadata", name="get_files_metadata", ) @login_required @@ -171,7 +177,7 @@ class _QueryParams(BaseModel): @routes.get( - _path_prefix + "/{location_id}/datasets/{dataset_id}/metadata", + _storage_locations_prefix + "/{location_id}/datasets/{dataset_id}/metadata", name="list_dataset_files_metadata", ) @login_required @@ -199,7 +205,7 @@ class _QueryParams(BaseModel): @routes.get( - _path_prefix + "/{location_id}/files/{file_id}/metadata", + _storage_locations_prefix + "/{location_id}/files/{file_id}/metadata", name="get_file_metadata", ) @login_required @@ -216,7 +222,7 @@ class _PathParams(BaseModel): @routes.get( - _path_prefix + "/{location_id}/files/{file_id}", + _storage_locations_prefix + "/{location_id}/files/{file_id}", name="download_file", ) @login_required @@ -238,7 +244,7 @@ class _QueryParams(BaseModel): @routes.put( - _path_prefix + "/{location_id}/files/{file_id}", + _storage_locations_prefix + "/{location_id}/files/{file_id}", name="upload_file", ) @login_required @@ -279,7 +285,7 @@ class _QueryParams(BaseModel): @routes.post( - _path_prefix + "/{location_id}/files/{file_id}:complete", + _storage_locations_prefix + "/{location_id}/files/{file_id}:complete", name="complete_upload_file", ) @login_required @@ -307,7 +313,7 @@ class _PathParams(BaseModel): @routes.post( - _path_prefix + "/{location_id}/files/{file_id}:abort", + _storage_locations_prefix + "/{location_id}/files/{file_id}:abort", name="abort_upload_file", ) @login_required @@ -324,7 +330,8 @@ class _PathParams(BaseModel): @routes.post( - _path_prefix + "/{location_id}/files/{file_id}:complete/futures/{future_id}", + _storage_locations_prefix + + "/{location_id}/files/{file_id}:complete/futures/{future_id}", name="is_completed_upload_file", ) @login_required @@ -342,7 +349,7 @@ class _PathParams(BaseModel): @routes.delete( - _path_prefix + "/{location_id}/files/{file_id}", + _storage_locations_prefix + "/{location_id}/files/{file_id}", name="delete_file", ) @login_required @@ -358,3 +365,20 @@ class _PathParams(BaseModel): request, "DELETE", body=None ) return create_data_response(payload, status=resp_status) + + +@routes.post( + _storage_prefix + "/export-data", + name="export_data", +) +@login_required +@permission_required("storage.files.*") +async def export_data(request: web.Request) -> web.Response: + rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) + data_export_post = await parse_request_body_as( + model_schema_cls=DataExportPost, request=request + ) + job_get = start_data_export( + rabbitmq_rpc_client=rabbitmq_rpc_client, + paths=data_export_post.to_storage_model(), + ) From 223631e368f10990d29ebaa8e937f0dff41e1aa4 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Sun, 16 Feb 2025 08:10:58 +0100 Subject: [PATCH 08/63] further renaming --- .../{data_export_tasks.py => data_export_async_jobs.py} | 0 .../rabbitmq/rpc_interfaces/async_jobs/async_jobs.py | 2 +- .../src/simcore_service_storage/api/rpc/_async_jobs.py | 2 +- .../src/simcore_service_storage/api/rpc/_data_export.py | 2 +- services/storage/tests/unit/test_data_export.py | 2 +- .../src/simcore_service_webserver/storage/_handlers.py | 7 ++++++- 6 files changed, 10 insertions(+), 5 deletions(-) rename packages/models-library/src/models_library/api_schemas_storage/{data_export_tasks.py => data_export_async_jobs.py} (100%) diff --git a/packages/models-library/src/models_library/api_schemas_storage/data_export_tasks.py b/packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py similarity index 100% rename from packages/models-library/src/models_library/api_schemas_storage/data_export_tasks.py rename to packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py index 1d2cd82d4af..d69abd73290 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py @@ -5,7 +5,7 @@ AsyncJobRpcResult, AsyncJobRpcStatus, ) -from models_library.api_schemas_storage.data_export_tasks import ( +from models_library.api_schemas_storage.data_export_async_jobs import ( DataExportTaskAbortOutput, ) from models_library.rabbitmq_basic_types import RPCMethodName, RPCNamespace diff --git a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py index 601563dac6f..9318236e408 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py @@ -6,7 +6,7 @@ AsyncJobRpcResult, AsyncJobRpcStatus, ) -from models_library.api_schemas_storage.data_export_tasks import ( +from models_library.api_schemas_storage.data_export_async_jobs import ( DataExportTaskAbortOutput, ) from servicelib.rabbitmq import RPCRouter diff --git a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py index 30abb07e04c..1e28b407630 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py @@ -2,7 +2,7 @@ from fastapi import FastAPI from models_library.api_schemas_rpc_data_export.async_jobs import AsyncJobRpcGet -from models_library.api_schemas_storage.data_export_tasks import ( +from models_library.api_schemas_storage.data_export_async_jobs import ( DataExportTaskStartInput, ) from servicelib.rabbitmq import RPCRouter diff --git a/services/storage/tests/unit/test_data_export.py b/services/storage/tests/unit/test_data_export.py index c43e94bf876..d140c776b74 100644 --- a/services/storage/tests/unit/test_data_export.py +++ b/services/storage/tests/unit/test_data_export.py @@ -13,7 +13,7 @@ AsyncJobRpcStatus, ) from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE -from models_library.api_schemas_storage.data_export_tasks import ( +from models_library.api_schemas_storage.data_export_async_jobs import ( DataExportTaskAbortOutput, DataExportTaskStartInput, ) diff --git a/services/web/server/src/simcore_service_webserver/storage/_handlers.py b/services/web/server/src/simcore_service_webserver/storage/_handlers.py index 4a5a7ce23ca..27052299842 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage/_handlers.py @@ -11,6 +11,7 @@ from aiohttp import ClientTimeout, web from models_library.projects_nodes_io import LocationID from models_library.storage_schemas import ( + AsyncJobGet, DataExportPost, FileUploadCompleteResponse, FileUploadCompletionBody, @@ -378,7 +379,11 @@ async def export_data(request: web.Request) -> web.Response: data_export_post = await parse_request_body_as( model_schema_cls=DataExportPost, request=request ) - job_get = start_data_export( + async_job_rpc_get = await start_data_export( rabbitmq_rpc_client=rabbitmq_rpc_client, paths=data_export_post.to_storage_model(), ) + return create_data_response( + AsyncJobGet.from_async_job_rpc_status(async_job_rpc_get), + status=status.HTTP_202_ACCEPTED, + ) From 0bd94d6ad8309be79635d3ee0a3c7b7036e064fd Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Sun, 16 Feb 2025 08:19:53 +0100 Subject: [PATCH 09/63] create openapi-specs make target for webserver to ensure github workflow generates latest version --- api/specs/web-server/Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api/specs/web-server/Makefile b/api/specs/web-server/Makefile index 4442c262d89..168d7c9ec78 100644 --- a/api/specs/web-server/Makefile +++ b/api/specs/web-server/Makefile @@ -16,3 +16,6 @@ install-dev install: _check_venv_active .PHONY: all all: _check_venv_active install python openapi.py + +.PHONY: openapi-specs +openapi-specs: all From b43b22dbfffe623d08721f69e7778742e0f68c58 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Sun, 16 Feb 2025 08:21:06 +0100 Subject: [PATCH 10/63] add webserver endpoint for triggering data export --- api/specs/web-server/_storage.py | 13 ++++++ .../src/models_library/storage_schemas.py | 6 +-- .../rpc_interfaces/storage/data_export.py | 2 +- .../api/v0/openapi.yaml | 42 +++++++++++++++++++ .../storage/_handlers.py | 2 +- 5 files changed, 59 insertions(+), 6 deletions(-) diff --git a/api/specs/web-server/_storage.py b/api/specs/web-server/_storage.py index 4f7b7ff11db..d0cc51ec397 100644 --- a/api/specs/web-server/_storage.py +++ b/api/specs/web-server/_storage.py @@ -10,6 +10,8 @@ from models_library.generics import Envelope from models_library.projects_nodes_io import LocationID from models_library.storage_schemas import ( + AsyncJobGet, + DataExportPost, FileLocation, FileMetaDataGet, FileUploadCompleteFutureResponse, @@ -167,3 +169,14 @@ async def is_completed_upload_file( location_id: LocationID, file_id: StorageFileIDStr, future_id: str ): """Returns state of upload completion""" + + +# data export +@router.post( + "/storage/export-data", + response_model=AsyncJobGet, + name="export_data", + description="Export data", +) +async def export_data(data_export: DataExportPost): + """Trigger data export. Returns async job id for getting status and results""" diff --git a/packages/models-library/src/models_library/storage_schemas.py b/packages/models-library/src/models_library/storage_schemas.py index 0f90566cbdd..070ba8f7d1c 100644 --- a/packages/models-library/src/models_library/storage_schemas.py +++ b/packages/models-library/src/models_library/storage_schemas.py @@ -15,7 +15,7 @@ from uuid import UUID from models_library.api_schemas_rpc_data_export.async_jobs import AsyncJobRpcGet -from models_library.api_schemas_storage.data_export_tasks import ( +from models_library.api_schemas_storage.data_export_async_jobs import ( DataExportTaskStartInput, ) from models_library.projects import ProjectID @@ -388,7 +388,5 @@ class AsyncJobGet(BaseModel): task_id: UUID @classmethod - def from_async_job_rpc_status( - cls, async_job_rpc_get: AsyncJobRpcGet - ) -> "AsyncJobGet": + def from_async_job_rpc_get(cls, async_job_rpc_get: AsyncJobRpcGet) -> "AsyncJobGet": return AsyncJobGet(task_id=async_job_rpc_get.task_id) diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py index 301640b1191..4c5ca709c28 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py @@ -2,7 +2,7 @@ from models_library.api_schemas_rpc_data_export.async_jobs import AsyncJobRpcGet from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE -from models_library.api_schemas_storage.data_export_tasks import ( +from models_library.api_schemas_storage.data_export_async_jobs import ( DataExportTaskStartInput, ) from models_library.rabbitmq_basic_types import RPCMethodName 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 12f2a2334a1..cb935e6780a 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 @@ -5973,6 +5973,26 @@ paths: application/json: schema: $ref: '#/components/schemas/Envelope_FileUploadCompleteFutureResponse_' + /v0/storage/export-data: + post: + tags: + - storage + summary: Export Data + description: Export data + operationId: export_data + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DataExportPost' + required: true + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/AsyncJobGet' /v0/trash: delete: tags: @@ -7001,6 +7021,16 @@ components: - app_name - version title: AppStatusCheck + AsyncJobGet: + properties: + task_id: + type: string + format: uuid + title: Task Id + type: object + required: + - task_id + title: AsyncJobGet Author: properties: name: @@ -7638,6 +7668,18 @@ components: - dataset title: DatCoreFileLink description: I/O port type to hold a link to a file in DATCORE storage + DataExportPost: + properties: + paths: + items: + type: string + format: path + type: array + title: Paths + type: object + required: + - paths + title: DataExportPost DatasetMetaData: properties: dataset_id: diff --git a/services/web/server/src/simcore_service_webserver/storage/_handlers.py b/services/web/server/src/simcore_service_webserver/storage/_handlers.py index 27052299842..5f9e30b04ab 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage/_handlers.py @@ -384,6 +384,6 @@ async def export_data(request: web.Request) -> web.Response: paths=data_export_post.to_storage_model(), ) return create_data_response( - AsyncJobGet.from_async_job_rpc_status(async_job_rpc_get), + AsyncJobGet.from_async_job_rpc_get(async_job_rpc_get), status=status.HTTP_202_ACCEPTED, ) From 9795838a738a2ce74849d0d6707459baaf429a9d Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Sun, 16 Feb 2025 09:12:54 +0100 Subject: [PATCH 11/63] add webserver endpoint for getting async job status --- api/specs/web-server/_storage.py | 12 +++- .../src/models_library/storage_schemas.py | 26 ++++++++- .../api/v0/openapi.yaml | 55 ++++++++++++++++++- .../storage/_handlers.py | 27 ++++++++- 4 files changed, 116 insertions(+), 4 deletions(-) diff --git a/api/specs/web-server/_storage.py b/api/specs/web-server/_storage.py index d0cc51ec397..a65dc32f578 100644 --- a/api/specs/web-server/_storage.py +++ b/api/specs/web-server/_storage.py @@ -11,6 +11,7 @@ from models_library.projects_nodes_io import LocationID from models_library.storage_schemas import ( AsyncJobGet, + AsyncJobStatus, DataExportPost, FileLocation, FileMetaDataGet, @@ -175,8 +176,17 @@ async def is_completed_upload_file( @router.post( "/storage/export-data", response_model=AsyncJobGet, - name="export_data", + name="storage_export_data", description="Export data", ) async def export_data(data_export: DataExportPost): """Trigger data export. Returns async job id for getting status and results""" + + +@router.get( + "/storage/async-jobs/status", + response_model=AsyncJobStatus, + name="storage_async_job_status", +) +async def get_async_job_status(task_id: AsyncJobGet): + """Get async job status""" diff --git a/packages/models-library/src/models_library/storage_schemas.py b/packages/models-library/src/models_library/storage_schemas.py index 070ba8f7d1c..14a2ddf2594 100644 --- a/packages/models-library/src/models_library/storage_schemas.py +++ b/packages/models-library/src/models_library/storage_schemas.py @@ -14,7 +14,10 @@ from typing import Annotated, Any, Literal, Self, TypeAlias from uuid import UUID -from models_library.api_schemas_rpc_data_export.async_jobs import AsyncJobRpcGet +from models_library.api_schemas_rpc_data_export.async_jobs import ( + AsyncJobRpcGet, + AsyncJobRpcStatus, +) from models_library.api_schemas_storage.data_export_async_jobs import ( DataExportTaskStartInput, ) @@ -25,6 +28,7 @@ ByteSize, ConfigDict, Field, + PositiveFloat, PositiveInt, RootModel, StringConstraints, @@ -390,3 +394,23 @@ class AsyncJobGet(BaseModel): @classmethod def from_async_job_rpc_get(cls, async_job_rpc_get: AsyncJobRpcGet) -> "AsyncJobGet": return AsyncJobGet(task_id=async_job_rpc_get.task_id) + + +class AsyncJobStatus(BaseModel): + task_id: UUID + task_progress: PositiveFloat = Field(..., ge=0.0, le=1.0) + done: bool + started: datetime + stopped: datetime | None + + @classmethod + def from_async_job_rpc_status( + cls, async_job_rpc_status: AsyncJobRpcStatus + ) -> "AsyncJobStatus": + return AsyncJobStatus( + task_id=async_job_rpc_status.task_id, + task_progress=async_job_rpc_status.task_progress, + done=async_job_rpc_status.done, + started=async_job_rpc_status.started, + stopped=async_job_rpc_status.stopped, + ) 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 cb935e6780a..27ddd65161d 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 @@ -5977,7 +5977,7 @@ paths: post: tags: - storage - summary: Export Data + summary: Storage Export Data description: Export data operationId: export_data requestBody: @@ -5993,6 +5993,26 @@ paths: application/json: schema: $ref: '#/components/schemas/AsyncJobGet' + /v0/storage/async-jobs/status: + get: + tags: + - storage + summary: Storage Async Job Status + description: Get async job status + operationId: get_async_job_status + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AsyncJobGet' + required: true + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/AsyncJobStatus' /v0/trash: delete: tags: @@ -7031,6 +7051,39 @@ components: required: - task_id title: AsyncJobGet + AsyncJobStatus: + properties: + task_id: + type: string + format: uuid + title: Task Id + task_progress: + type: number + maximum: 1.0 + minimum: 0.0 + exclusiveMinimum: true + title: Task Progress + done: + type: boolean + title: Done + started: + type: string + format: date-time + title: Started + stopped: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Stopped + type: object + required: + - task_id + - task_progress + - done + - started + - stopped + title: AsyncJobStatus Author: properties: name: diff --git a/services/web/server/src/simcore_service_webserver/storage/_handlers.py b/services/web/server/src/simcore_service_webserver/storage/_handlers.py index 5f9e30b04ab..fc1ebfb21af 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage/_handlers.py @@ -9,9 +9,11 @@ from urllib.parse import quote, unquote from aiohttp import ClientTimeout, web +from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE from models_library.projects_nodes_io import LocationID from models_library.storage_schemas import ( AsyncJobGet, + AsyncJobStatus, DataExportPost, FileUploadCompleteResponse, FileUploadCompletionBody, @@ -29,6 +31,7 @@ ) from servicelib.aiohttp.rest_responses import create_data_response from servicelib.common_headers import X_FORWARDED_PROTO +from servicelib.rabbitmq.rpc_interfaces.async_jobs.async_jobs import get_status from servicelib.rabbitmq.rpc_interfaces.storage.data_export import start_data_export from servicelib.request_keys import RQT_USERID_KEY from servicelib.rest_responses import unwrap_envelope @@ -370,7 +373,7 @@ class _PathParams(BaseModel): @routes.post( _storage_prefix + "/export-data", - name="export_data", + name="storage_export_data", ) @login_required @permission_required("storage.files.*") @@ -387,3 +390,25 @@ async def export_data(request: web.Request) -> web.Response: AsyncJobGet.from_async_job_rpc_get(async_job_rpc_get), status=status.HTTP_202_ACCEPTED, ) + + +@routes.get( + _storage_prefix + "/async-jobs/status", + name="storage_async_job_status", +) +@login_required +@permission_required("storage.files.*") +async def get_async_job_status(request: web.Request) -> web.Response: + rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) + async_job_get = await parse_request_body_as( + model_schema_cls=AsyncJobGet, request=request + ) + async_job_rpc_status = await get_status( + rabbitmq_rpc_client=rabbitmq_rpc_client, + rpc_namespace=STORAGE_RPC_NAMESPACE, + task_id=async_job_get.task_id, + ) + return create_data_response( + AsyncJobStatus.from_async_job_rpc_status(async_job_rpc_status), + status=status.HTTP_202_ACCEPTED, + ) From 224dfe2a7e447b493e345ee5951dfd87b2560e96 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Mon, 17 Feb 2025 09:30:00 +0100 Subject: [PATCH 12/63] minor fix --- .../server/src/simcore_service_webserver/storage/_handlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/server/src/simcore_service_webserver/storage/_handlers.py b/services/web/server/src/simcore_service_webserver/storage/_handlers.py index fc1ebfb21af..d15671b4dd7 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage/_handlers.py @@ -410,5 +410,5 @@ async def get_async_job_status(request: web.Request) -> web.Response: ) return create_data_response( AsyncJobStatus.from_async_job_rpc_status(async_job_rpc_status), - status=status.HTTP_202_ACCEPTED, + status=status.HTTP_200_OK, ) From 3fb022ee69ce78f6383e94cf1719ce4e33f0dd19 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Mon, 17 Feb 2025 10:45:28 +0100 Subject: [PATCH 13/63] add initial test --- .../src/models_library/storage_schemas.py | 5 +++ .../storage/_handlers.py | 4 +- .../unit/with_dbs/03/test_storage_rpc.py | 44 +++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py diff --git a/packages/models-library/src/models_library/storage_schemas.py b/packages/models-library/src/models_library/storage_schemas.py index 14a2ddf2594..0c300ed616c 100644 --- a/packages/models-library/src/models_library/storage_schemas.py +++ b/packages/models-library/src/models_library/storage_schemas.py @@ -32,6 +32,7 @@ PositiveInt, RootModel, StringConstraints, + field_serializer, field_validator, model_validator, ) @@ -384,6 +385,10 @@ class SoftCopyBody(BaseModel): class DataExportPost(BaseModel): paths: list[Path] + @field_serializer("paths") + def serialize_path(self, value: list[Path]) -> list[str]: + return [f"{elm}" for elm in value] + def to_storage_model(self) -> DataExportTaskStartInput: return DataExportTaskStartInput(paths=self.paths) diff --git a/services/web/server/src/simcore_service_webserver/storage/_handlers.py b/services/web/server/src/simcore_service_webserver/storage/_handlers.py index d15671b4dd7..f8c9caf7864 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage/_handlers.py @@ -375,8 +375,8 @@ class _PathParams(BaseModel): _storage_prefix + "/export-data", name="storage_export_data", ) -@login_required -@permission_required("storage.files.*") +# @login_required +# @permission_required("storage.files.*") async def export_data(request: web.Request) -> web.Response: rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) data_export_post = await parse_request_body_as( diff --git a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py new file mode 100644 index 00000000000..e282413f7ec --- /dev/null +++ b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py @@ -0,0 +1,44 @@ +from pathlib import Path +from typing import Any, Callable + +import pytest +from aiohttp.test_utils import TestClient +from faker import Faker +from models_library.api_schemas_rpc_data_export.async_jobs import ( + AsyncJobRpcGet, + AsyncJobRpcId, +) +from models_library.storage_schemas import DataExportPost +from pytest_mock import MockerFixture +from servicelib.aiohttp import status + + +@pytest.fixture +def create_storage_rpc_client_mock(mocker: MockerFixture) -> Callable[[str, Any], None]: + def _(method: str, result_or_exception: Any): + def side_effect(*args, **kwargs): + if isinstance(result_or_exception, Exception): + raise result_or_exception + return result_or_exception + + mocker.patch( + f"simcore_service_webserver.storage._handlers.{method}", + side_effect=side_effect, + ) + + return _ + + +async def test_data_export( + client: TestClient, + create_storage_rpc_client_mock: Callable[[str, Any], None], + faker: Faker, +): + _task_id = AsyncJobRpcId(faker.uuid4()) + create_storage_rpc_client_mock( + "start_data_export", AsyncJobRpcGet(task_id=_task_id, task_name=faker.text()) + ) + + _body = DataExportPost(paths=[Path(".")]) + response = await client.post("/v0/storage/export-data", json=_body.model_dump()) + assert response.status == status.HTTP_202_ACCEPTED From ac06db4a98df4cceca097204e6f9cf6eff319780 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Mon, 17 Feb 2025 11:09:07 +0100 Subject: [PATCH 14/63] pass login and permission in test --- .../src/simcore_service_webserver/storage/_handlers.py | 4 ++-- .../web/server/tests/unit/with_dbs/03/test_storage_rpc.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/storage/_handlers.py b/services/web/server/src/simcore_service_webserver/storage/_handlers.py index f8c9caf7864..d15671b4dd7 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage/_handlers.py @@ -375,8 +375,8 @@ class _PathParams(BaseModel): _storage_prefix + "/export-data", name="storage_export_data", ) -# @login_required -# @permission_required("storage.files.*") +@login_required +@permission_required("storage.files.*") async def export_data(request: web.Request) -> web.Response: rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) data_export_post = await parse_request_body_as( diff --git a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py index e282413f7ec..fc1287e2671 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py +++ b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py @@ -10,7 +10,9 @@ ) from models_library.storage_schemas import DataExportPost from pytest_mock import MockerFixture +from pytest_simcore.helpers.webserver_login import UserInfoDict from servicelib.aiohttp import status +from simcore_postgres_database.models.users import UserRole @pytest.fixture @@ -29,7 +31,10 @@ def side_effect(*args, **kwargs): return _ +@pytest.mark.parametrize("user_role", [UserRole.USER]) async def test_data_export( + user_role: UserRole, + logged_user: UserInfoDict, client: TestClient, create_storage_rpc_client_mock: Callable[[str, Any], None], faker: Faker, From 249967e2b20f48c80d9a34a6e07f3350b113f7b7 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Mon, 17 Feb 2025 11:33:32 +0100 Subject: [PATCH 15/63] add get status test --- api/specs/web-server/_storage.py | 4 +- .../src/models_library/storage_schemas.py | 5 -- .../unit/with_dbs/03/test_storage_rpc.py | 47 +++++++++++++++++-- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/api/specs/web-server/_storage.py b/api/specs/web-server/_storage.py index a65dc32f578..e6eeeacb1b6 100644 --- a/api/specs/web-server/_storage.py +++ b/api/specs/web-server/_storage.py @@ -175,7 +175,7 @@ async def is_completed_upload_file( # data export @router.post( "/storage/export-data", - response_model=AsyncJobGet, + response_model=Envelope[AsyncJobGet], name="storage_export_data", description="Export data", ) @@ -185,7 +185,7 @@ async def export_data(data_export: DataExportPost): @router.get( "/storage/async-jobs/status", - response_model=AsyncJobStatus, + response_model=Envelope[AsyncJobStatus], name="storage_async_job_status", ) async def get_async_job_status(task_id: AsyncJobGet): diff --git a/packages/models-library/src/models_library/storage_schemas.py b/packages/models-library/src/models_library/storage_schemas.py index 0c300ed616c..14a2ddf2594 100644 --- a/packages/models-library/src/models_library/storage_schemas.py +++ b/packages/models-library/src/models_library/storage_schemas.py @@ -32,7 +32,6 @@ PositiveInt, RootModel, StringConstraints, - field_serializer, field_validator, model_validator, ) @@ -385,10 +384,6 @@ class SoftCopyBody(BaseModel): class DataExportPost(BaseModel): paths: list[Path] - @field_serializer("paths") - def serialize_path(self, value: list[Path]) -> list[str]: - return [f"{elm}" for elm in value] - def to_storage_model(self) -> DataExportTaskStartInput: return DataExportTaskStartInput(paths=self.paths) diff --git a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py index fc1287e2671..b4d481fa284 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py +++ b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py @@ -1,3 +1,4 @@ +from datetime import datetime from pathlib import Path from typing import Any, Callable @@ -7,11 +8,15 @@ from models_library.api_schemas_rpc_data_export.async_jobs import ( AsyncJobRpcGet, AsyncJobRpcId, + AsyncJobRpcStatus, ) -from models_library.storage_schemas import DataExportPost +from models_library.generics import Envelope +from models_library.storage_schemas import AsyncJobGet, DataExportPost from pytest_mock import MockerFixture from pytest_simcore.helpers.webserver_login import UserInfoDict from servicelib.aiohttp import status +from servicelib.rabbitmq.rpc_interfaces.async_jobs.async_jobs import get_status +from servicelib.rabbitmq.rpc_interfaces.storage.data_export import start_data_export from simcore_postgres_database.models.users import UserRole @@ -41,9 +46,45 @@ async def test_data_export( ): _task_id = AsyncJobRpcId(faker.uuid4()) create_storage_rpc_client_mock( - "start_data_export", AsyncJobRpcGet(task_id=_task_id, task_name=faker.text()) + start_data_export.__name__, + AsyncJobRpcGet(task_id=_task_id, task_name=faker.text()), ) _body = DataExportPost(paths=[Path(".")]) - response = await client.post("/v0/storage/export-data", json=_body.model_dump()) + response = await client.post( + "/v0/storage/export-data", data=_body.model_dump_json() + ) assert response.status == status.HTTP_202_ACCEPTED + Envelope[AsyncJobGet].model_validate(await response.json()) + + +@pytest.mark.parametrize("user_role", [UserRole.USER]) +async def test_get_async_jobs_status( + user_role: UserRole, + logged_user: UserInfoDict, + client: TestClient, + create_storage_rpc_client_mock: Callable[[str, Any], None], + faker: Faker, +): + _task_id = AsyncJobRpcId(faker.uuid4()) + create_storage_rpc_client_mock( + get_status.__name__, + AsyncJobRpcStatus( + task_id=_task_id, + task_progress=0.5, + done=False, + started=datetime.now(), + stopped=None, + ), + ) + + _body = AsyncJobGet(task_id=_task_id) + response = await client.get( + "/v0/storage/async-jobs/status", data=_body.model_dump_json() + ) + assert response.status == status.HTTP_200_OK + response_body_data = ( + Envelope[AsyncJobGet].model_validate(await response.json()).data + ) + assert response_body_data is not None + assert response_body_data.task_id == _task_id From 8b154af2cb851e29c01f8fc3b75361906f7e9739 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Mon, 17 Feb 2025 13:53:14 +0100 Subject: [PATCH 16/63] add location id in webserver endpoint --- .../api_schemas_storage/data_export_async_jobs.py | 2 ++ .../src/models_library/storage_schemas.py | 4 ++-- .../src/simcore_service_webserver/storage/_handlers.py | 10 +++++++--- .../server/tests/unit/with_dbs/03/test_storage_rpc.py | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py b/packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py index 63c39888f4e..a662a53fac4 100644 --- a/packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py +++ b/packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py @@ -2,10 +2,12 @@ from pathlib import Path from models_library.api_schemas_rpc_data_export.async_jobs import AsyncJobRpcId +from models_library.projects_nodes_io import LocationID from pydantic import BaseModel, Field class DataExportTaskStartInput(BaseModel): + location_id: LocationID paths: list[Path] = Field(..., min_length=1) diff --git a/packages/models-library/src/models_library/storage_schemas.py b/packages/models-library/src/models_library/storage_schemas.py index 14a2ddf2594..9af0d3ee4a9 100644 --- a/packages/models-library/src/models_library/storage_schemas.py +++ b/packages/models-library/src/models_library/storage_schemas.py @@ -384,8 +384,8 @@ class SoftCopyBody(BaseModel): class DataExportPost(BaseModel): paths: list[Path] - def to_storage_model(self) -> DataExportTaskStartInput: - return DataExportTaskStartInput(paths=self.paths) + def to_storage_model(self, location_id: LocationID) -> DataExportTaskStartInput: + return DataExportTaskStartInput(paths=self.paths, location_id=location_id) class AsyncJobGet(BaseModel): diff --git a/services/web/server/src/simcore_service_webserver/storage/_handlers.py b/services/web/server/src/simcore_service_webserver/storage/_handlers.py index d15671b4dd7..6fe2339263f 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage/_handlers.py @@ -129,7 +129,7 @@ async def _forward_request_to_storage( routes = web.RouteTableDef() _storage_prefix = f"/{API_VTAG}/storage" -_storage_locations_prefix = f"/{_storage_prefix}/locations" +_storage_locations_prefix = f"{_storage_prefix}/locations" @routes.get(_storage_locations_prefix, name="list_storage_locations") @@ -372,19 +372,23 @@ class _PathParams(BaseModel): @routes.post( - _storage_prefix + "/export-data", + _storage_locations_prefix + "/{location_id}/export-data", name="storage_export_data", ) @login_required @permission_required("storage.files.*") async def export_data(request: web.Request) -> web.Response: + class _PathParams(BaseModel): + location_id: LocationID + rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) + _path_params = parse_request_path_parameters_as(_PathParams, request) data_export_post = await parse_request_body_as( model_schema_cls=DataExportPost, request=request ) async_job_rpc_get = await start_data_export( rabbitmq_rpc_client=rabbitmq_rpc_client, - paths=data_export_post.to_storage_model(), + paths=data_export_post.to_storage_model(location_id=_path_params.location_id), ) return create_data_response( AsyncJobGet.from_async_job_rpc_get(async_job_rpc_get), diff --git a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py index b4d481fa284..a8651097fce 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py +++ b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py @@ -52,7 +52,7 @@ async def test_data_export( _body = DataExportPost(paths=[Path(".")]) response = await client.post( - "/v0/storage/export-data", data=_body.model_dump_json() + "/v0/storage/locations/0/export-data", data=_body.model_dump_json() ) assert response.status == status.HTTP_202_ACCEPTED Envelope[AsyncJobGet].model_validate(await response.json()) From ac20937ebd3d5598e90fc9c78c597b9a26722779 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Mon, 17 Feb 2025 14:07:07 +0100 Subject: [PATCH 17/63] task_id -> job_id --- .../api_schemas_rpc_data_export/async_jobs.py | 4 ++-- .../src/models_library/storage_schemas.py | 8 ++++---- .../api/rpc/_async_jobs.py | 12 ++++++------ .../api/rpc/_data_export.py | 2 +- .../storage/tests/unit/test_data_export.py | 19 ++++++++++--------- .../storage/_handlers.py | 9 ++++----- .../unit/with_dbs/03/test_storage_rpc.py | 11 ++++------- 7 files changed, 31 insertions(+), 34 deletions(-) diff --git a/packages/models-library/src/models_library/api_schemas_rpc_data_export/async_jobs.py b/packages/models-library/src/models_library/api_schemas_rpc_data_export/async_jobs.py index 14fb658780e..8aa73c4bbbf 100644 --- a/packages/models-library/src/models_library/api_schemas_rpc_data_export/async_jobs.py +++ b/packages/models-library/src/models_library/api_schemas_rpc_data_export/async_jobs.py @@ -9,7 +9,7 @@ class AsyncJobRpcStatus(BaseModel): - task_id: AsyncJobRpcId + job_id: AsyncJobRpcId task_progress: PositiveFloat = Field(..., ge=0.0, le=1.0) done: bool started: datetime @@ -32,5 +32,5 @@ class AsyncJobRpcResult(BaseModel): class AsyncJobRpcGet(BaseModel): - task_id: AsyncJobRpcId + job_id: AsyncJobRpcId task_name: str diff --git a/packages/models-library/src/models_library/storage_schemas.py b/packages/models-library/src/models_library/storage_schemas.py index 9af0d3ee4a9..3842de0ddc2 100644 --- a/packages/models-library/src/models_library/storage_schemas.py +++ b/packages/models-library/src/models_library/storage_schemas.py @@ -389,15 +389,15 @@ def to_storage_model(self, location_id: LocationID) -> DataExportTaskStartInput: class AsyncJobGet(BaseModel): - task_id: UUID + job_id: UUID @classmethod def from_async_job_rpc_get(cls, async_job_rpc_get: AsyncJobRpcGet) -> "AsyncJobGet": - return AsyncJobGet(task_id=async_job_rpc_get.task_id) + return AsyncJobGet(job_id=async_job_rpc_get.job_id) class AsyncJobStatus(BaseModel): - task_id: UUID + job_id: UUID task_progress: PositiveFloat = Field(..., ge=0.0, le=1.0) done: bool started: datetime @@ -408,7 +408,7 @@ def from_async_job_rpc_status( cls, async_job_rpc_status: AsyncJobRpcStatus ) -> "AsyncJobStatus": return AsyncJobStatus( - task_id=async_job_rpc_status.task_id, + job_id=async_job_rpc_status.job_id, task_progress=async_job_rpc_status.task_progress, done=async_job_rpc_status.done, started=async_job_rpc_status.started, diff --git a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py index 9318236e408..1d24afba2e5 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py @@ -15,16 +15,16 @@ @router.expose() -async def abort(app: FastAPI, task_id: AsyncJobRpcId) -> DataExportTaskAbortOutput: +async def abort(app: FastAPI, job_id: AsyncJobRpcId) -> DataExportTaskAbortOutput: assert app # nosec - return DataExportTaskAbortOutput(result=True, task_id=task_id) + return DataExportTaskAbortOutput(result=True, task_id=job_id) @router.expose() -async def get_status(app: FastAPI, task_id: AsyncJobRpcId) -> AsyncJobRpcStatus: +async def get_status(app: FastAPI, job_id: AsyncJobRpcId) -> AsyncJobRpcStatus: assert app # nosec return AsyncJobRpcStatus( - task_id=task_id, + job_id=job_id, task_progress=0.5, done=False, started=datetime.now(), @@ -33,7 +33,7 @@ async def get_status(app: FastAPI, task_id: AsyncJobRpcId) -> AsyncJobRpcStatus: @router.expose() -async def get_result(app: FastAPI, task_id: AsyncJobRpcId) -> AsyncJobRpcResult: +async def get_result(app: FastAPI, job_id: AsyncJobRpcId) -> AsyncJobRpcResult: assert app # nosec - assert task_id # nosec + assert job_id # nosec return AsyncJobRpcResult(result="Here's your result.", error=None) diff --git a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py index 1e28b407630..5ed9f56afeb 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py @@ -16,6 +16,6 @@ async def start_data_export( ) -> AsyncJobRpcGet: assert app # nosec return AsyncJobRpcGet( - task_id=uuid4(), + job_id=uuid4(), task_name=", ".join(str(p) for p in paths.paths), ) diff --git a/services/storage/tests/unit/test_data_export.py b/services/storage/tests/unit/test_data_export.py index d140c776b74..2555b6536a8 100644 --- a/services/storage/tests/unit/test_data_export.py +++ b/services/storage/tests/unit/test_data_export.py @@ -77,32 +77,33 @@ async def rpc_client( async def test_start_data_export(rpc_client: RabbitMQRPCClient, faker: Faker): result = await data_export.start_data_export( - rpc_client, paths=DataExportTaskStartInput(paths=[Path(faker.file_path())]) + rpc_client, + paths=DataExportTaskStartInput(location_id=0, paths=[Path(faker.file_path())]), ) assert isinstance(result, AsyncJobRpcGet) async def test_abort_data_export(rpc_client: RabbitMQRPCClient, faker: Faker): - _task_id = AsyncJobRpcId(faker.uuid4()) + _job_id = AsyncJobRpcId(faker.uuid4()) result = await async_jobs.abort( - rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, task_id=_task_id + rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, task_id=_job_id ) assert isinstance(result, DataExportTaskAbortOutput) - assert result.task_id == _task_id + assert result.task_id == _job_id async def test_get_data_export_status(rpc_client: RabbitMQRPCClient, faker: Faker): - _task_id = AsyncJobRpcId(faker.uuid4()) + _job_id = AsyncJobRpcId(faker.uuid4()) result = await async_jobs.get_status( - rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, task_id=_task_id + rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, task_id=_job_id ) assert isinstance(result, AsyncJobRpcStatus) - assert result.task_id == _task_id + assert result.job_id == _job_id async def test_get_data_export_result(rpc_client: RabbitMQRPCClient, faker: Faker): - _task_id = AsyncJobRpcId(faker.uuid4()) + _job_id = AsyncJobRpcId(faker.uuid4()) result = await async_jobs.get_result( - rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, task_id=_task_id + rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, task_id=_job_id ) assert isinstance(result, AsyncJobRpcResult) diff --git a/services/web/server/src/simcore_service_webserver/storage/_handlers.py b/services/web/server/src/simcore_service_webserver/storage/_handlers.py index 6fe2339263f..e0e7b2f81d8 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage/_handlers.py @@ -397,20 +397,19 @@ class _PathParams(BaseModel): @routes.get( - _storage_prefix + "/async-jobs/status", + _storage_prefix + "/async-jobs/{job_id}/status", name="storage_async_job_status", ) @login_required @permission_required("storage.files.*") async def get_async_job_status(request: web.Request) -> web.Response: rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) - async_job_get = await parse_request_body_as( - model_schema_cls=AsyncJobGet, request=request - ) + + async_job_get = parse_request_path_parameters_as(AsyncJobGet, request) async_job_rpc_status = await get_status( rabbitmq_rpc_client=rabbitmq_rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, - task_id=async_job_get.task_id, + task_id=async_job_get.job_id, ) return create_data_response( AsyncJobStatus.from_async_job_rpc_status(async_job_rpc_status), diff --git a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py index a8651097fce..8672d431fa1 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py +++ b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py @@ -47,7 +47,7 @@ async def test_data_export( _task_id = AsyncJobRpcId(faker.uuid4()) create_storage_rpc_client_mock( start_data_export.__name__, - AsyncJobRpcGet(task_id=_task_id, task_name=faker.text()), + AsyncJobRpcGet(job_id=_task_id, task_name=faker.text()), ) _body = DataExportPost(paths=[Path(".")]) @@ -70,7 +70,7 @@ async def test_get_async_jobs_status( create_storage_rpc_client_mock( get_status.__name__, AsyncJobRpcStatus( - task_id=_task_id, + job_id=_task_id, task_progress=0.5, done=False, started=datetime.now(), @@ -78,13 +78,10 @@ async def test_get_async_jobs_status( ), ) - _body = AsyncJobGet(task_id=_task_id) - response = await client.get( - "/v0/storage/async-jobs/status", data=_body.model_dump_json() - ) + response = await client.get(f"/v0/storage/async-jobs/{_task_id}/status") assert response.status == status.HTTP_200_OK response_body_data = ( Envelope[AsyncJobGet].model_validate(await response.json()).data ) assert response_body_data is not None - assert response_body_data.task_id == _task_id + assert response_body_data.job_id == _task_id From 527e7a64b8e8824178c21ac76eff9756fc837382 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Mon, 17 Feb 2025 14:20:51 +0100 Subject: [PATCH 18/63] cleanup --- .../api_schemas_rpc_data_export/async_jobs.py | 5 ++++ .../data_export_async_jobs.py | 6 ----- .../rpc_interfaces/async_jobs/async_jobs.py | 20 +++++++--------- .../api/rpc/_async_jobs.py | 8 +++---- .../storage/tests/unit/test_data_export.py | 8 +++---- .../storage/_handlers.py | 24 +++++++++++++++++-- .../unit/with_dbs/03/test_storage_rpc.py | 12 +++++----- 7 files changed, 49 insertions(+), 34 deletions(-) diff --git a/packages/models-library/src/models_library/api_schemas_rpc_data_export/async_jobs.py b/packages/models-library/src/models_library/api_schemas_rpc_data_export/async_jobs.py index 8aa73c4bbbf..97714f3fea5 100644 --- a/packages/models-library/src/models_library/api_schemas_rpc_data_export/async_jobs.py +++ b/packages/models-library/src/models_library/api_schemas_rpc_data_export/async_jobs.py @@ -34,3 +34,8 @@ class AsyncJobRpcResult(BaseModel): class AsyncJobRpcGet(BaseModel): job_id: AsyncJobRpcId task_name: str + + +class AsyncJobRpcAbort(BaseModel): + result: bool + job_id: AsyncJobRpcId diff --git a/packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py b/packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py index a662a53fac4..b015a6a5a0e 100644 --- a/packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py +++ b/packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py @@ -1,7 +1,6 @@ # pylint: disable=R6301 from pathlib import Path -from models_library.api_schemas_rpc_data_export.async_jobs import AsyncJobRpcId from models_library.projects_nodes_io import LocationID from pydantic import BaseModel, Field @@ -9,8 +8,3 @@ class DataExportTaskStartInput(BaseModel): location_id: LocationID paths: list[Path] = Field(..., min_length=1) - - -class DataExportTaskAbortOutput(BaseModel): - result: bool - task_id: AsyncJobRpcId diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py index d69abd73290..9a7bef57ddd 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py @@ -1,13 +1,11 @@ from typing import Final from models_library.api_schemas_rpc_data_export.async_jobs import ( + AsyncJobRpcAbort, AsyncJobRpcId, AsyncJobRpcResult, AsyncJobRpcStatus, ) -from models_library.api_schemas_storage.data_export_async_jobs import ( - DataExportTaskAbortOutput, -) from models_library.rabbitmq_basic_types import RPCMethodName, RPCNamespace from pydantic import NonNegativeInt, TypeAdapter @@ -22,15 +20,15 @@ async def abort( rabbitmq_rpc_client: RabbitMQRPCClient, *, rpc_namespace: RPCNamespace, - task_id: AsyncJobRpcId -) -> DataExportTaskAbortOutput: + job_id: AsyncJobRpcId +) -> AsyncJobRpcAbort: result = await rabbitmq_rpc_client.request( rpc_namespace, _RPC_METHOD_NAME_ADAPTER.validate_python("abort"), - task_id=task_id, + job_id=job_id, timeout_s=_DEFAULT_TIMEOUT_S, ) - assert isinstance(result, DataExportTaskAbortOutput) + assert isinstance(result, AsyncJobRpcAbort) return result @@ -38,12 +36,12 @@ async def get_status( rabbitmq_rpc_client: RabbitMQRPCClient, *, rpc_namespace: RPCNamespace, - task_id: AsyncJobRpcId + job_id: AsyncJobRpcId ) -> AsyncJobRpcStatus: result = await rabbitmq_rpc_client.request( rpc_namespace, _RPC_METHOD_NAME_ADAPTER.validate_python("get_status"), - task_id=task_id, + job_id=job_id, timeout_s=_DEFAULT_TIMEOUT_S, ) assert isinstance(result, AsyncJobRpcStatus) @@ -54,12 +52,12 @@ async def get_result( rabbitmq_rpc_client: RabbitMQRPCClient, *, rpc_namespace: RPCNamespace, - task_id: AsyncJobRpcId + job_id: AsyncJobRpcId ) -> AsyncJobRpcResult: result = await rabbitmq_rpc_client.request( rpc_namespace, _RPC_METHOD_NAME_ADAPTER.validate_python("get_result"), - task_id=task_id, + job_id=job_id, timeout_s=_DEFAULT_TIMEOUT_S, ) assert isinstance(result, AsyncJobRpcResult) diff --git a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py index 1d24afba2e5..d6cc102b3e5 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py @@ -2,22 +2,20 @@ from fastapi import FastAPI from models_library.api_schemas_rpc_data_export.async_jobs import ( + AsyncJobRpcAbort, AsyncJobRpcId, AsyncJobRpcResult, AsyncJobRpcStatus, ) -from models_library.api_schemas_storage.data_export_async_jobs import ( - DataExportTaskAbortOutput, -) from servicelib.rabbitmq import RPCRouter router = RPCRouter() @router.expose() -async def abort(app: FastAPI, job_id: AsyncJobRpcId) -> DataExportTaskAbortOutput: +async def abort(app: FastAPI, job_id: AsyncJobRpcId) -> AsyncJobRpcAbort: assert app # nosec - return DataExportTaskAbortOutput(result=True, task_id=job_id) + return AsyncJobRpcAbort(result=True, job_id=job_id) @router.expose() diff --git a/services/storage/tests/unit/test_data_export.py b/services/storage/tests/unit/test_data_export.py index 2555b6536a8..6c62ed528c9 100644 --- a/services/storage/tests/unit/test_data_export.py +++ b/services/storage/tests/unit/test_data_export.py @@ -86,16 +86,16 @@ async def test_start_data_export(rpc_client: RabbitMQRPCClient, faker: Faker): async def test_abort_data_export(rpc_client: RabbitMQRPCClient, faker: Faker): _job_id = AsyncJobRpcId(faker.uuid4()) result = await async_jobs.abort( - rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, task_id=_job_id + rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, job_id=_job_id ) assert isinstance(result, DataExportTaskAbortOutput) - assert result.task_id == _job_id + assert result.job_id == _job_id async def test_get_data_export_status(rpc_client: RabbitMQRPCClient, faker: Faker): _job_id = AsyncJobRpcId(faker.uuid4()) result = await async_jobs.get_status( - rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, task_id=_job_id + rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, job_id=_job_id ) assert isinstance(result, AsyncJobRpcStatus) assert result.job_id == _job_id @@ -104,6 +104,6 @@ async def test_get_data_export_status(rpc_client: RabbitMQRPCClient, faker: Fake async def test_get_data_export_result(rpc_client: RabbitMQRPCClient, faker: Faker): _job_id = AsyncJobRpcId(faker.uuid4()) result = await async_jobs.get_result( - rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, task_id=_job_id + rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, job_id=_job_id ) assert isinstance(result, AsyncJobRpcResult) diff --git a/services/web/server/src/simcore_service_webserver/storage/_handlers.py b/services/web/server/src/simcore_service_webserver/storage/_handlers.py index e0e7b2f81d8..b8067b15a16 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage/_handlers.py @@ -31,7 +31,7 @@ ) from servicelib.aiohttp.rest_responses import create_data_response from servicelib.common_headers import X_FORWARDED_PROTO -from servicelib.rabbitmq.rpc_interfaces.async_jobs.async_jobs import get_status +from servicelib.rabbitmq.rpc_interfaces.async_jobs.async_jobs import abort, get_status from servicelib.rabbitmq.rpc_interfaces.storage.data_export import start_data_export from servicelib.request_keys import RQT_USERID_KEY from servicelib.rest_responses import unwrap_envelope @@ -409,7 +409,27 @@ async def get_async_job_status(request: web.Request) -> web.Response: async_job_rpc_status = await get_status( rabbitmq_rpc_client=rabbitmq_rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, - task_id=async_job_get.job_id, + job_id=async_job_get.job_id, + ) + return create_data_response( + AsyncJobStatus.from_async_job_rpc_status(async_job_rpc_status), + status=status.HTTP_200_OK, + ) + + +@routes.post( + _storage_prefix + "/async-jobs/{job_id}:abort", + name="storage_async_job_abort", +) +@login_required +@permission_required("storage.files.*") +async def abort_async_job(request: web.Request) -> web.Response: + rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) + async_job_get = parse_request_path_parameters_as(AsyncJobGet, request) + data_export_task_abort_request = await abort( + rabbitmq_rpc_client=rabbitmq_rpc_client, + rpc_namespace=STORAGE_RPC_NAMESPACE, + job_id=async_job_get.job_id, ) return create_data_response( AsyncJobStatus.from_async_job_rpc_status(async_job_rpc_status), diff --git a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py index 8672d431fa1..be122a4dd24 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py +++ b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py @@ -44,10 +44,10 @@ async def test_data_export( create_storage_rpc_client_mock: Callable[[str, Any], None], faker: Faker, ): - _task_id = AsyncJobRpcId(faker.uuid4()) + _job_id = AsyncJobRpcId(faker.uuid4()) create_storage_rpc_client_mock( start_data_export.__name__, - AsyncJobRpcGet(job_id=_task_id, task_name=faker.text()), + AsyncJobRpcGet(job_id=_job_id, task_name=faker.text()), ) _body = DataExportPost(paths=[Path(".")]) @@ -66,11 +66,11 @@ async def test_get_async_jobs_status( create_storage_rpc_client_mock: Callable[[str, Any], None], faker: Faker, ): - _task_id = AsyncJobRpcId(faker.uuid4()) + _job_id = AsyncJobRpcId(faker.uuid4()) create_storage_rpc_client_mock( get_status.__name__, AsyncJobRpcStatus( - job_id=_task_id, + job_id=_job_id, task_progress=0.5, done=False, started=datetime.now(), @@ -78,10 +78,10 @@ async def test_get_async_jobs_status( ), ) - response = await client.get(f"/v0/storage/async-jobs/{_task_id}/status") + response = await client.get(f"/v0/storage/async-jobs/{_job_id}/status") assert response.status == status.HTTP_200_OK response_body_data = ( Envelope[AsyncJobGet].model_validate(await response.json()).data ) assert response_body_data is not None - assert response_body_data.job_id == _task_id + assert response_body_data.job_id == _job_id From 999907e7004216c74ecf9c17abb8422ee1279c96 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Mon, 17 Feb 2025 14:45:21 +0100 Subject: [PATCH 19/63] add test for abort endpoint --- .../storage/_handlers.py | 9 ++++--- .../unit/with_dbs/03/test_storage_rpc.py | 26 ++++++++++++++++++- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/storage/_handlers.py b/services/web/server/src/simcore_service_webserver/storage/_handlers.py index b8067b15a16..a03382db918 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage/_handlers.py @@ -426,12 +426,13 @@ async def get_async_job_status(request: web.Request) -> web.Response: async def abort_async_job(request: web.Request) -> web.Response: rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) async_job_get = parse_request_path_parameters_as(AsyncJobGet, request) - data_export_task_abort_request = await abort( + async_job_rpc_abort = await abort( rabbitmq_rpc_client=rabbitmq_rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, job_id=async_job_get.job_id, ) - return create_data_response( - AsyncJobStatus.from_async_job_rpc_status(async_job_rpc_status), - status=status.HTTP_200_OK, + return web.Response( + status=status.HTTP_200_OK + if async_job_rpc_abort.result + else status.HTTP_500_INTERNAL_SERVER_ERROR ) diff --git a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py index be122a4dd24..8ecb79b6af5 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py +++ b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py @@ -6,6 +6,7 @@ from aiohttp.test_utils import TestClient from faker import Faker from models_library.api_schemas_rpc_data_export.async_jobs import ( + AsyncJobRpcAbort, AsyncJobRpcGet, AsyncJobRpcId, AsyncJobRpcStatus, @@ -15,7 +16,7 @@ from pytest_mock import MockerFixture from pytest_simcore.helpers.webserver_login import UserInfoDict from servicelib.aiohttp import status -from servicelib.rabbitmq.rpc_interfaces.async_jobs.async_jobs import get_status +from servicelib.rabbitmq.rpc_interfaces.async_jobs.async_jobs import abort, get_status from servicelib.rabbitmq.rpc_interfaces.storage.data_export import start_data_export from simcore_postgres_database.models.users import UserRole @@ -85,3 +86,26 @@ async def test_get_async_jobs_status( ) assert response_body_data is not None assert response_body_data.job_id == _job_id + + +@pytest.mark.parametrize("user_role", [UserRole.USER]) +@pytest.mark.parametrize("abort_success", [True, False]) +async def test_abort_async_jobs( + user_role: UserRole, + logged_user: UserInfoDict, + client: TestClient, + create_storage_rpc_client_mock: Callable[[str, Any], None], + faker: Faker, + abort_success: bool, +): + _job_id = AsyncJobRpcId(faker.uuid4()) + create_storage_rpc_client_mock( + abort.__name__, AsyncJobRpcAbort(result=abort_success, job_id=_job_id) + ) + + response = await client.post(f"/v0/storage/async-jobs/{_job_id}:abort") + + if abort_success: + assert response.status == status.HTTP_200_OK + else: + assert response.status == status.HTTP_500_INTERNAL_SERVER_ERROR From 520c0438d6ebedf43f8ba2b11281aa591232cb8e Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Mon, 17 Feb 2025 15:25:29 +0100 Subject: [PATCH 20/63] add test for endpoint of getting result --- .../src/models_library/storage_schemas.py | 14 ++++++++++ .../storage/_handlers.py | 27 ++++++++++++++++++- .../unit/with_dbs/03/test_storage_rpc.py | 25 ++++++++++++++++- 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/packages/models-library/src/models_library/storage_schemas.py b/packages/models-library/src/models_library/storage_schemas.py index 3842de0ddc2..c6e236be697 100644 --- a/packages/models-library/src/models_library/storage_schemas.py +++ b/packages/models-library/src/models_library/storage_schemas.py @@ -16,6 +16,7 @@ from models_library.api_schemas_rpc_data_export.async_jobs import ( AsyncJobRpcGet, + AsyncJobRpcResult, AsyncJobRpcStatus, ) from models_library.api_schemas_storage.data_export_async_jobs import ( @@ -414,3 +415,16 @@ def from_async_job_rpc_status( started=async_job_rpc_status.started, stopped=async_job_rpc_status.stopped, ) + + +class AsyncJobResult(BaseModel): + result: Any | None + error: Any | None + + @classmethod + def from_async_job_rpc_result( + cls, async_job_rpc_result: AsyncJobRpcResult + ) -> "AsyncJobResult": + return AsyncJobResult( + result=async_job_rpc_result.result, error=async_job_rpc_result.error + ) diff --git a/services/web/server/src/simcore_service_webserver/storage/_handlers.py b/services/web/server/src/simcore_service_webserver/storage/_handlers.py index a03382db918..3cdac261433 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage/_handlers.py @@ -13,6 +13,7 @@ from models_library.projects_nodes_io import LocationID from models_library.storage_schemas import ( AsyncJobGet, + AsyncJobResult, AsyncJobStatus, DataExportPost, FileUploadCompleteResponse, @@ -31,7 +32,11 @@ ) from servicelib.aiohttp.rest_responses import create_data_response from servicelib.common_headers import X_FORWARDED_PROTO -from servicelib.rabbitmq.rpc_interfaces.async_jobs.async_jobs import abort, get_status +from servicelib.rabbitmq.rpc_interfaces.async_jobs.async_jobs import ( + abort, + get_result, + get_status, +) from servicelib.rabbitmq.rpc_interfaces.storage.data_export import start_data_export from servicelib.request_keys import RQT_USERID_KEY from servicelib.rest_responses import unwrap_envelope @@ -436,3 +441,23 @@ async def abort_async_job(request: web.Request) -> web.Response: if async_job_rpc_abort.result else status.HTTP_500_INTERNAL_SERVER_ERROR ) + + +@routes.get( + _storage_prefix + "/async-jobs/{job_id}/result", + name="storage_async_job_result", +) +@login_required +@permission_required("storage.files.*") +async def get_async_job_result(request: web.Request) -> web.Response: + rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) + async_job_get = parse_request_path_parameters_as(AsyncJobGet, request) + async_job_rpc_result = await get_result( + rabbitmq_rpc_client=rabbitmq_rpc_client, + rpc_namespace=STORAGE_RPC_NAMESPACE, + job_id=async_job_get.job_id, + ) + return create_data_response( + AsyncJobResult.from_async_job_rpc_result(async_job_rpc_result), + status=status.HTTP_200_OK, + ) diff --git a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py index 8ecb79b6af5..3e2c0c7f824 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py +++ b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py @@ -9,6 +9,7 @@ AsyncJobRpcAbort, AsyncJobRpcGet, AsyncJobRpcId, + AsyncJobRpcResult, AsyncJobRpcStatus, ) from models_library.generics import Envelope @@ -16,7 +17,11 @@ from pytest_mock import MockerFixture from pytest_simcore.helpers.webserver_login import UserInfoDict from servicelib.aiohttp import status -from servicelib.rabbitmq.rpc_interfaces.async_jobs.async_jobs import abort, get_status +from servicelib.rabbitmq.rpc_interfaces.async_jobs.async_jobs import ( + abort, + get_result, + get_status, +) from servicelib.rabbitmq.rpc_interfaces.storage.data_export import start_data_export from simcore_postgres_database.models.users import UserRole @@ -109,3 +114,21 @@ async def test_abort_async_jobs( assert response.status == status.HTTP_200_OK else: assert response.status == status.HTTP_500_INTERNAL_SERVER_ERROR + + +@pytest.mark.parametrize("user_role", [UserRole.USER]) +async def test_get_async_job_result( + user_role: UserRole, + logged_user: UserInfoDict, + client: TestClient, + create_storage_rpc_client_mock: Callable[[str, Any], None], + faker: Faker, +): + _job_id = AsyncJobRpcId(faker.uuid4()) + create_storage_rpc_client_mock( + get_result.__name__, AsyncJobRpcResult(result=None, error=faker.text()) + ) + + response = await client.get(f"/v0/storage/async-jobs/{_job_id}/result") + + assert response.status == status.HTTP_200_OK From 1b42a8f6aa95f559b4fb358fca5a066b10cf15e6 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Mon, 17 Feb 2025 15:28:53 +0100 Subject: [PATCH 21/63] update webserver openapi specs --- .../api/v0/openapi.yaml | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) 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 27ddd65161d..9966b11c6b5 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 @@ -5992,7 +5992,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/AsyncJobGet' + $ref: '#/components/schemas/Envelope_AsyncJobGet_' /v0/storage/async-jobs/status: get: tags: @@ -6012,7 +6012,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/AsyncJobStatus' + $ref: '#/components/schemas/Envelope_AsyncJobStatus_' /v0/trash: delete: tags: @@ -7043,20 +7043,20 @@ components: title: AppStatusCheck AsyncJobGet: properties: - task_id: + job_id: type: string format: uuid - title: Task Id + title: Job Id type: object required: - - task_id + - job_id title: AsyncJobGet AsyncJobStatus: properties: - task_id: + job_id: type: string format: uuid - title: Task Id + title: Job Id task_progress: type: number maximum: 1.0 @@ -7078,7 +7078,7 @@ components: title: Stopped type: object required: - - task_id + - job_id - task_progress - done - started @@ -7860,6 +7860,32 @@ components: title: Error type: object title: Envelope[AppStatusCheck] + Envelope_AsyncJobGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/AsyncJobGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[AsyncJobGet] + Envelope_AsyncJobStatus_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/AsyncJobStatus' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[AsyncJobStatus] Envelope_CatalogServiceGet_: properties: data: From a649c71306788a832f0924f41e1cc8c03669a5f9 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Mon, 17 Feb 2025 16:18:46 +0100 Subject: [PATCH 22/63] start adding exceptions and propagating them --- .../data_export_async_jobs.py | 20 +++++++++++ .../api/rpc/_data_export.py | 11 ++++++- .../unit/with_dbs/03/test_storage_rpc.py | 33 ++++++++++++++++--- 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py b/packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py index b015a6a5a0e..2c015fdc61b 100644 --- a/packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py +++ b/packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py @@ -1,6 +1,7 @@ # pylint: disable=R6301 from pathlib import Path +from common_library.errors_classes import OsparcErrorMixin from models_library.projects_nodes_io import LocationID from pydantic import BaseModel, Field @@ -8,3 +9,22 @@ class DataExportTaskStartInput(BaseModel): location_id: LocationID paths: list[Path] = Field(..., min_length=1) + + +### Exceptions + + +class StorageRpcErrors(OsparcErrorMixin, RuntimeError): + pass + + +class InvalidFileIdentifierError(StorageRpcErrors): + msg_template: str = "Could not find the file {file_id}" + + +class AccessRightError(StorageRpcErrors): + msg_template: str = "User {user_id} does not have access to file {file_id}" + + +class DataExportError(StorageRpcErrors): + msg_template: str = "Could not complete data export job with id {job_id}" diff --git a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py index 5ed9f56afeb..35f26c414e6 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py @@ -3,14 +3,23 @@ from fastapi import FastAPI from models_library.api_schemas_rpc_data_export.async_jobs import AsyncJobRpcGet from models_library.api_schemas_storage.data_export_async_jobs import ( + AccessRightError, + DataExportError, DataExportTaskStartInput, + InvalidFileIdentifierError, ) from servicelib.rabbitmq import RPCRouter router = RPCRouter() -@router.expose() +@router.expose( + reraise_if_error_type=( + InvalidFileIdentifierError, + AccessRightError, + DataExportError, + ) +) async def start_data_export( app: FastAPI, paths: DataExportTaskStartInput ) -> AsyncJobRpcGet: diff --git a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py index 3e2c0c7f824..444d642d9da 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py +++ b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py @@ -12,6 +12,11 @@ AsyncJobRpcResult, AsyncJobRpcStatus, ) +from models_library.api_schemas_storage.data_export_async_jobs import ( + AccessRightError, + DataExportError, + InvalidFileIdentifierError, +) from models_library.generics import Envelope from models_library.storage_schemas import AsyncJobGet, DataExportPost from pytest_mock import MockerFixture @@ -25,6 +30,8 @@ from servicelib.rabbitmq.rpc_interfaces.storage.data_export import start_data_export from simcore_postgres_database.models.users import UserRole +_faker = Faker() + @pytest.fixture def create_storage_rpc_client_mock(mocker: MockerFixture) -> Callable[[str, Any], None]: @@ -43,25 +50,43 @@ def side_effect(*args, **kwargs): @pytest.mark.parametrize("user_role", [UserRole.USER]) +@pytest.mark.parametrize( + "backend_result_or_exception", + [ + AsyncJobRpcGet(job_id=AsyncJobRpcId(_faker.uuid4()), task_name=_faker.text()), + InvalidFileIdentifierError(file_id=Path("/my/file")), + AccessRightError(user_id=_faker.pyint(min_value=0), file_id=Path("/my/file")), + DataExportError(job_id=_faker.pyint(min_value=0)), + ], + ids=lambda x: type(x).__name__, +) async def test_data_export( user_role: UserRole, logged_user: UserInfoDict, client: TestClient, create_storage_rpc_client_mock: Callable[[str, Any], None], faker: Faker, + backend_result_or_exception: Any, ): - _job_id = AsyncJobRpcId(faker.uuid4()) create_storage_rpc_client_mock( start_data_export.__name__, - AsyncJobRpcGet(job_id=_job_id, task_name=faker.text()), + backend_result_or_exception, ) _body = DataExportPost(paths=[Path(".")]) response = await client.post( "/v0/storage/locations/0/export-data", data=_body.model_dump_json() ) - assert response.status == status.HTTP_202_ACCEPTED - Envelope[AsyncJobGet].model_validate(await response.json()) + if isinstance(backend_result_or_exception, AsyncJobRpcGet): + assert response.status == status.HTTP_202_ACCEPTED + Envelope[AsyncJobGet].model_validate(await response.json()) + elif isinstance(backend_result_or_exception, InvalidFileIdentifierError): + assert response.status == status.HTTP_404_NOT_FOUND + elif isinstance(backend_result_or_exception, AccessRightError): + assert response.status == status.HTTP_403_FORBIDDEN + else: + assert isinstance(backend_result_or_exception, DataExportError) + assert response.status == status.HTTP_500_INTERNAL_SERVER_ERROR @pytest.mark.parametrize("user_role", [UserRole.USER]) From 4ad6aac0b97d28cf40515461b22ead2e4c275ff5 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Tue, 18 Feb 2025 10:28:00 +0100 Subject: [PATCH 23/63] add exception handling to endpoint for triggering data export --- .../storage/_exception_handlers.py | 32 +++++++++++++++++++ .../storage/_handlers.py | 5 +++ 2 files changed, 37 insertions(+) create mode 100644 services/web/server/src/simcore_service_webserver/storage/_exception_handlers.py diff --git a/services/web/server/src/simcore_service_webserver/storage/_exception_handlers.py b/services/web/server/src/simcore_service_webserver/storage/_exception_handlers.py new file mode 100644 index 00000000000..d6cecb75d9e --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/storage/_exception_handlers.py @@ -0,0 +1,32 @@ +from models_library.api_schemas_storage.data_export_async_jobs import ( + AccessRightError, + DataExportError, + InvalidFileIdentifierError, +) +from servicelib.aiohttp import status +from simcore_service_webserver.exception_handling import ( + ExceptionToHttpErrorMap, + HttpErrorInfo, + exception_handling_decorator, + to_exceptions_handlers_map, +) + +_TO_HTTP_ERROR_MAP: ExceptionToHttpErrorMap = { + InvalidFileIdentifierError: HttpErrorInfo( + status.HTTP_404_NOT_FOUND, + "Could not find file.", + ), + AccessRightError: HttpErrorInfo( + status.HTTP_403_FORBIDDEN, + "Accessright error.", + ), + DataExportError: HttpErrorInfo( + status.HTTP_500_INTERNAL_SERVER_ERROR, + "Could not export data.", + ), +} + + +handle_data_export_exceptions = exception_handling_decorator( + to_exceptions_handlers_map(_TO_HTTP_ERROR_MAP) +) diff --git a/services/web/server/src/simcore_service_webserver/storage/_handlers.py b/services/web/server/src/simcore_service_webserver/storage/_handlers.py index 3cdac261433..48c05a439d8 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage/_handlers.py @@ -46,6 +46,7 @@ from .._meta import API_VTAG from ..login.decorators import login_required from ..security.decorators import permission_required +from ._exception_handlers import handle_data_export_exceptions from .schemas import StorageFileIDStr from .settings import StorageSettings, get_plugin_settings @@ -382,6 +383,7 @@ class _PathParams(BaseModel): ) @login_required @permission_required("storage.files.*") +@handle_data_export_exceptions async def export_data(request: web.Request) -> web.Response: class _PathParams(BaseModel): location_id: LocationID @@ -407,6 +409,7 @@ class _PathParams(BaseModel): ) @login_required @permission_required("storage.files.*") +@handle_data_export_exceptions async def get_async_job_status(request: web.Request) -> web.Response: rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) @@ -428,6 +431,7 @@ async def get_async_job_status(request: web.Request) -> web.Response: ) @login_required @permission_required("storage.files.*") +@handle_data_export_exceptions async def abort_async_job(request: web.Request) -> web.Response: rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) async_job_get = parse_request_path_parameters_as(AsyncJobGet, request) @@ -449,6 +453,7 @@ async def abort_async_job(request: web.Request) -> web.Response: ) @login_required @permission_required("storage.files.*") +@handle_data_export_exceptions async def get_async_job_result(request: web.Request) -> web.Response: rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) async_job_get = parse_request_path_parameters_as(AsyncJobGet, request) From 33b5ccfba486f7932f8a2cd782dff2be92b0f389 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Tue, 18 Feb 2025 10:43:56 +0100 Subject: [PATCH 24/63] remove 'data_export' from async jobs model lib section --- .../__init__.py | 0 .../async_jobs.py | 0 .../models-library/src/models_library/storage_schemas.py | 2 +- .../rabbitmq/rpc_interfaces/async_jobs/async_jobs.py | 2 +- .../rabbitmq/rpc_interfaces/storage/data_export.py | 2 +- .../src/simcore_service_storage/api/rpc/_async_jobs.py | 2 +- .../src/simcore_service_storage/api/rpc/_data_export.py | 2 +- services/storage/tests/unit/test_data_export.py | 6 +++--- .../web/server/tests/unit/with_dbs/03/test_storage_rpc.py | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) rename packages/models-library/src/models_library/{api_schemas_rpc_data_export => api_schemas_rpc_async_jobs}/__init__.py (100%) rename packages/models-library/src/models_library/{api_schemas_rpc_data_export => api_schemas_rpc_async_jobs}/async_jobs.py (100%) diff --git a/packages/models-library/src/models_library/api_schemas_rpc_data_export/__init__.py b/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/__init__.py similarity index 100% rename from packages/models-library/src/models_library/api_schemas_rpc_data_export/__init__.py rename to packages/models-library/src/models_library/api_schemas_rpc_async_jobs/__init__.py diff --git a/packages/models-library/src/models_library/api_schemas_rpc_data_export/async_jobs.py b/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py similarity index 100% rename from packages/models-library/src/models_library/api_schemas_rpc_data_export/async_jobs.py rename to packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py diff --git a/packages/models-library/src/models_library/storage_schemas.py b/packages/models-library/src/models_library/storage_schemas.py index c6e236be697..60b233a2a91 100644 --- a/packages/models-library/src/models_library/storage_schemas.py +++ b/packages/models-library/src/models_library/storage_schemas.py @@ -14,7 +14,7 @@ from typing import Annotated, Any, Literal, Self, TypeAlias from uuid import UUID -from models_library.api_schemas_rpc_data_export.async_jobs import ( +from models_library.api_schemas_rpc_async_jobs.async_jobs import ( AsyncJobRpcGet, AsyncJobRpcResult, AsyncJobRpcStatus, diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py index 9a7bef57ddd..f922976505a 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py @@ -1,6 +1,6 @@ from typing import Final -from models_library.api_schemas_rpc_data_export.async_jobs import ( +from models_library.api_schemas_rpc_async_jobs.async_jobs import ( AsyncJobRpcAbort, AsyncJobRpcId, AsyncJobRpcResult, diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py index 4c5ca709c28..7c8f2530682 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py @@ -1,6 +1,6 @@ from typing import Final -from models_library.api_schemas_rpc_data_export.async_jobs import AsyncJobRpcGet +from models_library.api_schemas_rpc_async_jobs.async_jobs import AsyncJobRpcGet from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE from models_library.api_schemas_storage.data_export_async_jobs import ( DataExportTaskStartInput, diff --git a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py index d6cc102b3e5..823007d3972 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py @@ -1,7 +1,7 @@ from datetime import datetime from fastapi import FastAPI -from models_library.api_schemas_rpc_data_export.async_jobs import ( +from models_library.api_schemas_rpc_async_jobs.async_jobs import ( AsyncJobRpcAbort, AsyncJobRpcId, AsyncJobRpcResult, diff --git a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py index 35f26c414e6..42d05d587f0 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py @@ -1,7 +1,7 @@ from uuid import uuid4 from fastapi import FastAPI -from models_library.api_schemas_rpc_data_export.async_jobs import AsyncJobRpcGet +from models_library.api_schemas_rpc_async_jobs.async_jobs import AsyncJobRpcGet from models_library.api_schemas_storage.data_export_async_jobs import ( AccessRightError, DataExportError, diff --git a/services/storage/tests/unit/test_data_export.py b/services/storage/tests/unit/test_data_export.py index 6c62ed528c9..56a71c0b39b 100644 --- a/services/storage/tests/unit/test_data_export.py +++ b/services/storage/tests/unit/test_data_export.py @@ -6,7 +6,8 @@ import pytest from faker import Faker from fastapi import FastAPI -from models_library.api_schemas_rpc_data_export.async_jobs import ( +from models_library.api_schemas_rpc_async_jobs.async_jobs import ( + AsyncJobRpcAbort, AsyncJobRpcGet, AsyncJobRpcId, AsyncJobRpcResult, @@ -14,7 +15,6 @@ ) from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE from models_library.api_schemas_storage.data_export_async_jobs import ( - DataExportTaskAbortOutput, DataExportTaskStartInput, ) from pytest_mock import MockerFixture @@ -88,7 +88,7 @@ async def test_abort_data_export(rpc_client: RabbitMQRPCClient, faker: Faker): result = await async_jobs.abort( rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, job_id=_job_id ) - assert isinstance(result, DataExportTaskAbortOutput) + assert isinstance(result, AsyncJobRpcAbort) assert result.job_id == _job_id diff --git a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py index 444d642d9da..0604e918041 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py +++ b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py @@ -5,7 +5,7 @@ import pytest from aiohttp.test_utils import TestClient from faker import Faker -from models_library.api_schemas_rpc_data_export.async_jobs import ( +from models_library.api_schemas_rpc_async_jobs.async_jobs import ( AsyncJobRpcAbort, AsyncJobRpcGet, AsyncJobRpcId, From 661b3b224f536e605b28d09b1d9fd4317a978137 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Tue, 18 Feb 2025 11:14:37 +0100 Subject: [PATCH 25/63] add exception handling for remaining endpoints --- .../api_schemas_rpc_async_jobs/exceptions.py | 13 ++++ .../api/rpc/_async_jobs.py | 8 ++- .../unit/with_dbs/03/test_storage_rpc.py | 69 +++++++++++++------ 3 files changed, 66 insertions(+), 24 deletions(-) create mode 100644 packages/models-library/src/models_library/api_schemas_rpc_async_jobs/exceptions.py diff --git a/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/exceptions.py b/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/exceptions.py new file mode 100644 index 00000000000..5902bf31738 --- /dev/null +++ b/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/exceptions.py @@ -0,0 +1,13 @@ +from common_library.errors_classes import OsparcErrorMixin + + +class BaseAsyncjobRpcError(OsparcErrorMixin, RuntimeError): + pass + + +class StatusError(BaseAsyncjobRpcError): + msg_template: str = "Could not get status of job {job_id}" + + +class ResultError(BaseAsyncjobRpcError): + msg_template: str = "Could not get results of job {job_id}" diff --git a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py index 823007d3972..b008720e579 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py @@ -7,6 +7,10 @@ AsyncJobRpcResult, AsyncJobRpcStatus, ) +from models_library.api_schemas_rpc_async_jobs.exceptions import ( + ResultError, + StatusError, +) from servicelib.rabbitmq import RPCRouter router = RPCRouter() @@ -18,7 +22,7 @@ async def abort(app: FastAPI, job_id: AsyncJobRpcId) -> AsyncJobRpcAbort: return AsyncJobRpcAbort(result=True, job_id=job_id) -@router.expose() +@router.expose(reraise_if_error_type=(StatusError,)) async def get_status(app: FastAPI, job_id: AsyncJobRpcId) -> AsyncJobRpcStatus: assert app # nosec return AsyncJobRpcStatus( @@ -30,7 +34,7 @@ async def get_status(app: FastAPI, job_id: AsyncJobRpcId) -> AsyncJobRpcStatus: ) -@router.expose() +@router.expose(reraise_if_error_type=(ResultError,)) async def get_result(app: FastAPI, job_id: AsyncJobRpcId) -> AsyncJobRpcResult: assert app # nosec assert job_id # nosec diff --git a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py index 0604e918041..e39077ae243 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py +++ b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py @@ -12,6 +12,10 @@ AsyncJobRpcResult, AsyncJobRpcStatus, ) +from models_library.api_schemas_rpc_async_jobs.exceptions import ( + ResultError, + StatusError, +) from models_library.api_schemas_storage.data_export_async_jobs import ( AccessRightError, DataExportError, @@ -90,32 +94,41 @@ async def test_data_export( @pytest.mark.parametrize("user_role", [UserRole.USER]) -async def test_get_async_jobs_status( - user_role: UserRole, - logged_user: UserInfoDict, - client: TestClient, - create_storage_rpc_client_mock: Callable[[str, Any], None], - faker: Faker, -): - _job_id = AsyncJobRpcId(faker.uuid4()) - create_storage_rpc_client_mock( - get_status.__name__, +@pytest.mark.parametrize( + "backend_result_or_exception", + [ AsyncJobRpcStatus( - job_id=_job_id, + job_id=_faker.uuid4(), task_progress=0.5, done=False, started=datetime.now(), stopped=None, ), - ) + StatusError(job_id=_faker.uuid4()), + ], + ids=lambda x: type(x).__name__, +) +async def test_get_async_jobs_status( + user_role: UserRole, + logged_user: UserInfoDict, + client: TestClient, + create_storage_rpc_client_mock: Callable[[str, Any], None], + backend_result_or_exception: Any, +): + _job_id = AsyncJobRpcId(_faker.uuid4()) + create_storage_rpc_client_mock(get_status.__name__, backend_result_or_exception) response = await client.get(f"/v0/storage/async-jobs/{_job_id}/status") - assert response.status == status.HTTP_200_OK - response_body_data = ( - Envelope[AsyncJobGet].model_validate(await response.json()).data - ) - assert response_body_data is not None - assert response_body_data.job_id == _job_id + if isinstance(backend_result_or_exception, AsyncJobRpcStatus): + assert response.status == status.HTTP_200_OK + response_body_data = ( + Envelope[AsyncJobGet].model_validate(await response.json()).data + ) + assert response_body_data is not None + elif isinstance(backend_result_or_exception, StatusError): + assert response.status == status.HTTP_500_INTERNAL_SERVER_ERROR + else: + raise Exception("Test incorrectly configured") @pytest.mark.parametrize("user_role", [UserRole.USER]) @@ -142,18 +155,30 @@ async def test_abort_async_jobs( @pytest.mark.parametrize("user_role", [UserRole.USER]) +@pytest.mark.parametrize( + "backend_result_or_exception", + [ + AsyncJobRpcResult(result=None, error=_faker.text()), + ResultError(job_id=_faker.uuid4()), + ], + ids=lambda x: type(x).__name__, +) async def test_get_async_job_result( user_role: UserRole, logged_user: UserInfoDict, client: TestClient, create_storage_rpc_client_mock: Callable[[str, Any], None], faker: Faker, + backend_result_or_exception: Any, ): _job_id = AsyncJobRpcId(faker.uuid4()) - create_storage_rpc_client_mock( - get_result.__name__, AsyncJobRpcResult(result=None, error=faker.text()) - ) + create_storage_rpc_client_mock(get_result.__name__, backend_result_or_exception) response = await client.get(f"/v0/storage/async-jobs/{_job_id}/result") - assert response.status == status.HTTP_200_OK + if isinstance(backend_result_or_exception, AsyncJobRpcResult): + assert response.status == status.HTTP_200_OK + elif isinstance(backend_result_or_exception, ResultError): + assert response.status == status.HTTP_500_INTERNAL_SERVER_ERROR + else: + raise Exception("Test incorrectly configured") From e19eea7daf3fe83a38f803e9a1a4038ff489e8d8 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Tue, 18 Feb 2025 11:23:49 +0100 Subject: [PATCH 26/63] update openapi specs of webserver --- .../api/v0/openapi.yaml | 15773 ++++++++++++++++ 1 file changed, 15773 insertions(+) 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 e69de29bb2d..4ed1250b114 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 @@ -0,0 +1,15773 @@ +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.58.0 +servers: +- url: '' + description: webserver +- url: http://{host}:{port} + description: development server + variables: + host: + default: localhost + port: + default: '8001' +paths: + /v0/auth/request-account: + post: + tags: + - auth + summary: Request Product Account + operationId: request_product_account + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AccountRequestInfo' + required: true + responses: + '204': + description: Successful Response + /v0/auth/register/invitations:check: + post: + tags: + - auth + summary: Check Registration Invitation + description: Check invitation and returns associated email or None + operationId: auth_check_registration_invitation + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/InvitationCheck' + required: true + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_InvitationInfo_' + /v0/auth/register: + post: + tags: + - auth + summary: Register + description: User registration + operationId: auth_register + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterBody' + required: true + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_Log_' + /v0/auth/unregister: + post: + tags: + - auth + summary: Unregister Account + operationId: unregister_account + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UnregisterCheck' + required: true + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_Log_' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + /v0/auth/verify-phone-number: + post: + tags: + - auth + summary: Register Phone + description: user tries to verify phone number for 2 Factor Authentication when + registering + operationId: auth_register_phone + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RegisterPhoneBody' + required: true + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_RegisterPhoneNextPage_' + /v0/auth/validate-code-register: + post: + tags: + - auth + summary: Phone Confirmation + description: user enters 2 Factor Authentication code when registering + operationId: auth_phone_confirmation + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PhoneConfirmationBody' + required: true + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_Log_' + /v0/auth/login: + post: + tags: + - auth + summary: Login + description: user logs in + operationId: auth_login + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/LoginBody' + required: true + responses: + '201': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_LoginNextPage_' + '401': + description: unauthorized reset due to invalid token code + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + /v0/auth/validate-code-login: + post: + tags: + - auth + summary: Login 2Fa + description: user enters 2 Factor Authentication code when login in + operationId: auth_login_2fa + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/LoginTwoFactorAuthBody' + required: true + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_Log_' + '401': + description: unauthorized reset due to invalid token code + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + /v0/auth/two_factor:resend: + post: + tags: + - auth + summary: Resend 2Fa Code + description: Resends 2FA either via email or sms + operationId: auth_resend_2fa_code + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Resend2faBody' + required: true + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_Log_' + '401': + description: unauthorized reset due to invalid token code + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + /v0/auth/logout: + post: + tags: + - auth + summary: Logout + description: user logout + operationId: auth_logout + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/LogoutBody' + required: true + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_Log_' + /v0/auth:check: + get: + tags: + - auth + summary: Check Auth + description: checks if user is authenticated in the platform + operationId: check_authentication + responses: + '204': + description: Successful Response + '401': + description: unauthorized reset due to invalid token code + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + /v0/auth/reset-password: + post: + tags: + - auth + summary: Reset Password + description: a non logged-in user requests a password reset + operationId: auth_reset_password + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ResetPasswordBody' + required: true + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_Log_' + '503': + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + /v0/auth/reset-password/{code}: + post: + tags: + - auth + summary: Reset Password Allowed + description: changes password using a token code without being logged in + operationId: auth_reset_password_allowed + parameters: + - name: code + in: path + required: true + schema: + type: string + title: Code + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ResetPasswordConfirmation' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_Log_' + '401': + description: unauthorized reset due to invalid token code + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + /v0/auth/change-password: + post: + tags: + - auth + summary: Change Password + description: logged in user changes password + operationId: auth_change_password + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ChangePasswordBody' + required: true + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_Log_' + '401': + description: unauthorized user. Login required + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + '409': + description: mismatch between new and confirmation passwords + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + '422': + description: current password is invalid + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + /v0/auth/confirmation/{code}: + get: + tags: + - auth + summary: Email Confirmation + description: email link sent to user to confirm an action + operationId: auth_confirmation + parameters: + - name: code + in: path + required: true + schema: + type: string + title: Code + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_Log_' + 3XX: + description: redirection to specific ui application page + /v0/auth/captcha: + get: + tags: + - auth + summary: Request Captcha + operationId: request_captcha + responses: + '200': + description: Successful Response + content: + application/json: + schema: {} + image/png: {} + /v0/auth/api-keys: + get: + tags: + - auth + summary: List Api Keys + description: lists API keys by this user + operationId: list_api_keys + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_ApiKeyGet__' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + post: + tags: + - auth + summary: Create Api Key + description: creates API keys to access public API + operationId: create_api_key + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ApiKeyCreateRequest' + required: true + responses: + '201': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_ApiKeyCreateResponse_' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + /v0/auth/api-keys/{api_key_id}: + get: + tags: + - auth + summary: Get Api Key + description: returns the API Key with the given ID + operationId: get_api_key + parameters: + - name: api_key_id + in: path + required: true + schema: + type: string + minLength: 1 + maxLength: 100 + title: Api Key Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_ApiKeyGet_' + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + delete: + tags: + - auth + summary: Delete Api Key + description: deletes the API key with the given ID + operationId: delete_api_key + parameters: + - name: api_key_id + in: path + required: true + schema: + type: string + minLength: 1 + maxLength: 100 + title: Api Key Id + responses: + '204': + description: Successful Response + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + /v0/groups: + get: + tags: + - groups + summary: List Groups + description: List all groups (organizations, primary, everyone and products) + I belong to + operationId: list_groups + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_MyGroupsGet_' + post: + tags: + - groups + summary: Create Group + description: Creates an organization group + operationId: create_group + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/GroupCreate' + required: true + responses: + '201': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_GroupGet_' + /v0/groups/{gid}: + get: + tags: + - groups + summary: Get Group + description: Get an organization group + operationId: get_group + parameters: + - name: gid + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Gid + minimum: 0 + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_GroupGet_' + patch: + tags: + - groups + summary: Update Group + description: Updates organization groups + operationId: update_group + parameters: + - name: gid + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Gid + minimum: 0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GroupUpdate' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_GroupGet_' + delete: + tags: + - groups + summary: Delete Group + description: Deletes organization groups + operationId: delete_group + parameters: + - name: gid + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Gid + minimum: 0 + responses: + '204': + description: Successful Response + /v0/groups/{gid}/users: + get: + tags: + - groups + - users + summary: Get All Group Users + description: Gets users in organization or primary groups + operationId: get_all_group_users + parameters: + - name: gid + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Gid + minimum: 0 + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_GroupUserGet__' + post: + tags: + - groups + - users + summary: Add Group User + description: Adds a user to an organization group using their username, user + ID, or email (subject to privacy settings) + operationId: add_group_user + parameters: + - name: gid + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Gid + minimum: 0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GroupUserAdd' + responses: + '204': + description: Successful Response + /v0/groups/{gid}/users/{uid}: + get: + tags: + - groups + - users + summary: Get Group User + description: Gets specific user in an organization group + operationId: get_group_user + parameters: + - name: gid + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Gid + minimum: 0 + - name: uid + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Uid + minimum: 0 + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_GroupUserGet_' + patch: + tags: + - groups + - users + summary: Update Group User + description: Updates user (access-rights) to an organization group + operationId: update_group_user + parameters: + - name: gid + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Gid + minimum: 0 + - name: uid + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Uid + minimum: 0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GroupUserUpdate' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_GroupUserGet_' + delete: + tags: + - groups + - users + summary: Delete Group User + description: Removes a user from an organization group + operationId: delete_group_user + parameters: + - name: gid + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Gid + minimum: 0 + - name: uid + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Uid + minimum: 0 + responses: + '204': + description: Successful Response + /v0/groups/{gid}/classifiers: + get: + tags: + - groups + summary: Get Group Classifiers + operationId: get_group_classifiers + parameters: + - name: gid + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Gid + minimum: 0 + - name: tree_view + in: query + required: false + schema: + const: std + type: string + default: std + title: Tree View + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_dict_str__Any__' + /v0/groups/sparc/classifiers/scicrunch-resources/{rrid}: + get: + tags: + - groups + summary: Get Scicrunch Resource + operationId: get_scicrunch_resource + parameters: + - name: rrid + in: path + required: true + schema: + type: string + title: Rrid + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_ResearchResource_' + post: + tags: + - groups + summary: Add Scicrunch Resource + operationId: add_scicrunch_resource + parameters: + - name: rrid + in: path + required: true + schema: + type: string + title: Rrid + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_ResearchResource_' + /v0/groups/sparc/classifiers/scicrunch-resources:search: + get: + tags: + - groups + summary: Search Scicrunch Resources + operationId: search_scicrunch_resources + parameters: + - name: guess_name + in: query + required: true + schema: + type: string + title: Guess Name + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_ResourceHit__' + /v0/tags: + get: + tags: + - tags + summary: List Tags + operationId: list_tags + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_TagGet__' + post: + tags: + - tags + summary: Create Tag + operationId: create_tag + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/TagCreate' + required: true + responses: + '201': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_TagGet_' + /v0/tags/{tag_id}: + patch: + tags: + - tags + summary: Update Tag + operationId: update_tag + parameters: + - name: tag_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Tag Id + minimum: 0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TagUpdate' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_TagGet_' + delete: + tags: + - tags + summary: Delete Tag + operationId: delete_tag + parameters: + - name: tag_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Tag Id + minimum: 0 + responses: + '204': + description: Successful Response + /v0/tags/{tag_id}/groups: + get: + tags: + - tags + - groups + summary: List Tag Groups + description: Lists all groups associated to this tag + operationId: list_tag_groups + parameters: + - name: tag_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Tag Id + minimum: 0 + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_TagGroupGet__' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + /v0/tags/{tag_id}/groups/{group_id}: + post: + tags: + - tags + - groups + summary: Create Tag Group + description: Shares tag `tag_id` with an organization or user with `group_id` + providing access-rights to it + operationId: create_tag_group + parameters: + - name: tag_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Tag Id + minimum: 0 + - name: group_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Group Id + minimum: 0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TagGroupCreate' + responses: + '201': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_TagGet_' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + put: + tags: + - tags + - groups + summary: Replace Tag Group + description: Replace access rights on tag for associated organization or user + with `group_id` + operationId: replace_tag_group + parameters: + - name: tag_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Tag Id + minimum: 0 + - name: group_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Group Id + minimum: 0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TagGroupCreate' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_TagGroupGet__' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + delete: + tags: + - tags + - groups + summary: Delete Tag Group + description: Delete access rights on tag to an associated organization or user + with `group_id` + operationId: delete_tag_group + parameters: + - name: tag_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Tag Id + minimum: 0 + - name: group_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Group Id + minimum: 0 + responses: + '204': + description: Successful Response + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + /v0/credits-price: + get: + tags: + - products + summary: Get Current Product Price + operationId: get_current_product_price + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_GetCreditPrice_' + /v0/products/{product_name}: + get: + tags: + - products + - po + summary: Get Product + description: 'NOTE: `/products/current` is used to define current project w/o + naming it' + operationId: get_product + parameters: + - name: product_name + in: path + required: true + schema: + anyOf: + - type: string + minLength: 1 + maxLength: 100 + - const: current + type: string + title: Product Name + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_ProductGet_' + /v0/products/current/ui: + get: + tags: + - products + summary: Get Current Product Ui + operationId: get_current_product_ui + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_ProductUIGet_' + /v0/products/{product_name}/templates/{template_id}: + put: + tags: + - products + - po + summary: Update Product Template + operationId: update_product_template + parameters: + - name: product_name + in: path + required: true + schema: + anyOf: + - type: string + minLength: 1 + maxLength: 100 + - const: current + type: string + title: Product Name + - name: template_id + in: path + required: true + schema: + type: string + minLength: 1 + maxLength: 100 + title: Template Id + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateProductTemplate' + responses: + '204': + description: Successful Response + /v0/invitation:generate: + post: + tags: + - products + - po + summary: Generate Invitation + operationId: generate_invitation + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/GenerateInvitation' + required: true + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_InvitationGenerated_' + /v0/me: + get: + tags: + - users + summary: Get My Profile + operationId: get_my_profile + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_MyProfileGet_' + put: + tags: + - users + summary: Replace My Profile + description: Use PATCH instead + operationId: replace_my_profile + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/MyProfilePatch' + required: true + responses: + '204': + description: Successful Response + deprecated: true + patch: + tags: + - users + summary: Update My Profile + operationId: update_my_profile + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/MyProfilePatch' + required: true + responses: + '204': + description: Successful Response + /v0/me/preferences/{preference_id}: + patch: + tags: + - users + summary: Set Frontend Preference + operationId: set_frontend_preference + parameters: + - name: preference_id + in: path + required: true + schema: + type: string + title: Preference Id + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PatchRequestBody' + responses: + '204': + description: Successful Response + /v0/me/tokens: + get: + tags: + - users + summary: List Tokens + operationId: list_tokens + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_MyTokenGet__' + post: + tags: + - users + summary: Create Token + operationId: create_token + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/MyTokenCreate' + required: true + responses: + '201': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_MyTokenGet_' + /v0/me/tokens/{service}: + get: + tags: + - users + summary: Get Token + operationId: get_token + parameters: + - name: service + in: path + required: true + schema: + type: string + title: Service + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_MyTokenGet_' + delete: + tags: + - users + summary: Delete Token + operationId: delete_token + parameters: + - name: service + in: path + required: true + schema: + type: string + title: Service + responses: + '204': + description: Successful Response + /v0/me/notifications: + get: + tags: + - users + summary: List User Notifications + operationId: list_user_notifications + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_UserNotification__' + post: + tags: + - users + summary: Create User Notification + operationId: create_user_notification + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UserNotificationCreate' + required: true + responses: + '204': + description: Successful Response + /v0/me/notifications/{notification_id}: + patch: + tags: + - users + summary: Mark Notification As Read + operationId: mark_notification_as_read + parameters: + - name: notification_id + in: path + required: true + schema: + type: string + title: Notification Id + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UserNotificationPatch' + responses: + '204': + description: Successful Response + /v0/me/permissions: + get: + tags: + - users + summary: List User Permissions + operationId: list_user_permissions + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_MyPermissionGet__' + /v0/users:search: + post: + tags: + - users + summary: Search Users + description: Search among users who are publicly visible to the caller (i.e., + me) based on their privacy settings. + operationId: search_users + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UsersSearch' + required: true + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_UserGet__' + /v0/admin/users:search: + get: + tags: + - users + - admin + summary: Search Users For Admin + operationId: search_users_for_admin + parameters: + - name: email + in: query + required: true + schema: + type: string + minLength: 3 + maxLength: 200 + title: Email + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_UserForAdminGet__' + /v0/admin/users:pre-register: + post: + tags: + - users + - admin + summary: Pre Register User For Admin + operationId: pre_register_user_for_admin + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PreRegisteredUserGet' + required: true + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_UserForAdminGet_' + /v0/wallets: + get: + tags: + - wallets + summary: List Wallets + operationId: list_wallets + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_WalletGetWithAvailableCredits__' + post: + tags: + - wallets + summary: Create Wallet + operationId: create_wallet + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CreateWalletBodyParams' + required: true + responses: + '201': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_WalletGet_' + /v0/wallets/default: + get: + tags: + - wallets + summary: Get Default Wallet + operationId: get_default_wallet + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_WalletGetWithAvailableCredits_' + /v0/wallets/{wallet_id}: + get: + tags: + - wallets + summary: Get Wallet + operationId: get_wallet + parameters: + - name: wallet_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Wallet Id + minimum: 0 + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_WalletGetWithAvailableCredits_' + put: + tags: + - wallets + summary: Update Wallet + operationId: update_wallet + parameters: + - name: wallet_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Wallet Id + minimum: 0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PutWalletBodyParams' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_WalletGet_' + /v0/wallets/{wallet_id}/payments: + post: + tags: + - wallets + summary: Create Payment + description: Creates payment to wallet `wallet_id` + operationId: create_payment + parameters: + - name: wallet_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Wallet Id + minimum: 0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateWalletPayment' + responses: + '202': + description: Payment initialized + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_WalletPaymentInitiated_' + /v0/wallets/-/payments: + get: + tags: + - wallets + summary: List All Payments + description: Lists all user payments to his/her wallets (only the ones he/she + created) + operationId: list_all_payments + parameters: + - name: limit + in: query + required: false + schema: + type: integer + minimum: 1 + exclusiveMaximum: true + default: 20 + title: Limit + maximum: 50 + - name: offset + in: query + required: false + schema: + type: integer + minimum: 0 + default: 0 + title: Offset + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Page_PaymentTransaction_' + /v0/wallets/{wallet_id}/payments/{payment_id}/invoice-link: + get: + tags: + - wallets + summary: Get Payment Invoice Link + operationId: get_payment_invoice_link + parameters: + - name: wallet_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Wallet Id + minimum: 0 + - name: payment_id + in: path + required: true + schema: + type: string + minLength: 1 + maxLength: 100 + title: Payment Id + responses: + '302': + description: redirection to invoice download link + content: + application/json: + schema: {} + /v0/wallets/{wallet_id}/payments/{payment_id}:cancel: + post: + tags: + - wallets + summary: Cancel Payment + operationId: cancel_payment + parameters: + - name: wallet_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Wallet Id + minimum: 0 + - name: payment_id + in: path + required: true + schema: + type: string + minLength: 1 + maxLength: 100 + title: Payment Id + responses: + '204': + description: Successfully cancelled + /v0/wallets/{wallet_id}/payments-methods:init: + post: + tags: + - wallets + summary: Init Creation Of Payment Method + operationId: init_creation_of_payment_method + parameters: + - name: wallet_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Wallet Id + minimum: 0 + responses: + '202': + description: Successfully initialized + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_PaymentMethodInitiated_' + /v0/wallets/{wallet_id}/payments-methods/{payment_method_id}:cancel: + post: + tags: + - wallets + summary: Cancel Creation Of Payment Method + operationId: cancel_creation_of_payment_method + parameters: + - name: wallet_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Wallet Id + minimum: 0 + - name: payment_method_id + in: path + required: true + schema: + type: string + minLength: 1 + maxLength: 100 + title: Payment Method Id + responses: + '204': + description: Successfully cancelled + /v0/wallets/{wallet_id}/payments-methods: + get: + tags: + - wallets + summary: List Payments Methods + description: Lists all payments method associated to `wallet_id` + operationId: list_payments_methods + parameters: + - name: wallet_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Wallet Id + minimum: 0 + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_PaymentMethodGet__' + /v0/wallets/{wallet_id}/payments-methods/{payment_method_id}: + get: + tags: + - wallets + summary: Get Payment Method + operationId: get_payment_method + parameters: + - name: wallet_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Wallet Id + minimum: 0 + - name: payment_method_id + in: path + required: true + schema: + type: string + minLength: 1 + maxLength: 100 + title: Payment Method Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_PaymentMethodGet_' + delete: + tags: + - wallets + summary: Delete Payment Method + operationId: delete_payment_method + parameters: + - name: wallet_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Wallet Id + minimum: 0 + - name: payment_method_id + in: path + required: true + schema: + type: string + minLength: 1 + maxLength: 100 + title: Payment Method Id + responses: + '204': + description: Successfully deleted + /v0/wallets/{wallet_id}/payments-methods/{payment_method_id}:pay: + post: + tags: + - wallets + summary: Pay With Payment Method + operationId: pay_with_payment_method + parameters: + - name: wallet_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Wallet Id + minimum: 0 + - name: payment_method_id + in: path + required: true + schema: + type: string + minLength: 1 + maxLength: 100 + title: Payment Method Id + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateWalletPayment' + responses: + '202': + description: Pay with payment-method + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_WalletPaymentInitiated_' + /v0/wallets/{wallet_id}/auto-recharge: + get: + tags: + - wallets + summary: Get Wallet Autorecharge + operationId: get_wallet_autorecharge + parameters: + - name: wallet_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Wallet Id + minimum: 0 + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_GetWalletAutoRecharge_' + put: + tags: + - wallets + summary: Replace Wallet Autorecharge + operationId: replace_wallet_autorecharge + parameters: + - name: wallet_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Wallet Id + minimum: 0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ReplaceWalletAutoRecharge' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_GetWalletAutoRecharge_' + /v0/wallets/{wallet_id}/groups/{group_id}: + post: + tags: + - wallets + - groups + summary: Create Wallet Group + operationId: create_wallet_group + parameters: + - name: wallet_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Wallet Id + minimum: 0 + - name: group_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Group Id + minimum: 0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/_WalletsGroupsBodyParams' + responses: + '201': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_WalletGroupGet_' + put: + tags: + - wallets + - groups + summary: Update Wallet Group + operationId: update_wallet_group + parameters: + - name: wallet_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Wallet Id + minimum: 0 + - name: group_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Group Id + minimum: 0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/_WalletsGroupsBodyParams' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_WalletGroupGet_' + delete: + tags: + - wallets + - groups + summary: Delete Wallet Group + operationId: delete_wallet_group + parameters: + - name: wallet_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Wallet Id + minimum: 0 + - name: group_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Group Id + minimum: 0 + responses: + '204': + description: Successful Response + /v0/wallets/{wallet_id}/groups: + get: + tags: + - wallets + - groups + summary: List Wallet Groups + operationId: list_wallet_groups + parameters: + - name: wallet_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Wallet Id + minimum: 0 + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_WalletGroupGet__' + /v0/activity/status: + get: + tags: + - tasks + summary: Get Activity Status + operationId: get_activity_status + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_dict_UUID__Activity__' + /v0/announcements: + get: + tags: + - announcements + summary: List Announcements + operationId: list_announcements + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_Announcement__' + /v0/catalog/services/-/latest: + get: + tags: + - catalog + summary: List Services Latest + operationId: list_services_latest + parameters: + - name: limit + in: query + required: false + schema: + type: integer + minimum: 1 + exclusiveMaximum: true + default: 20 + title: Limit + maximum: 50 + - name: offset + in: query + required: false + schema: + type: integer + minimum: 0 + default: 0 + title: Offset + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Page_CatalogServiceGet_' + /v0/catalog/services/{service_key}/{service_version}: + get: + tags: + - catalog + summary: Get Service + operationId: get_service + parameters: + - name: service_key + in: path + required: true + schema: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Service Key + - name: service_version + in: path + required: true + schema: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Service Version + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_CatalogServiceGet_' + patch: + tags: + - catalog + summary: Update Service + operationId: update_service + parameters: + - name: service_key + in: path + required: true + schema: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Service Key + - name: service_version + in: path + required: true + schema: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Service Version + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CatalogServiceUpdate' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_CatalogServiceGet_' + /v0/catalog/services/{service_key}/{service_version}/inputs: + get: + tags: + - catalog + summary: List Service Inputs + operationId: list_service_inputs + parameters: + - name: service_key + in: path + required: true + schema: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Service Key + - name: service_version + in: path + required: true + schema: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Service Version + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_ServiceInputGet__' + /v0/catalog/services/{service_key}/{service_version}/inputs/{input_key}: + get: + tags: + - catalog + summary: Get Service Input + operationId: get_service_input + parameters: + - name: service_key + in: path + required: true + schema: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Service Key + - name: service_version + in: path + required: true + schema: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Service Version + - name: input_key + in: path + required: true + schema: + type: string + pattern: ^[-_a-zA-Z0-9]+$ + title: Input Key + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_ServiceInputGet_' + /v0/catalog/services/{service_key}/{service_version}/inputs:match: + get: + tags: + - catalog + summary: Get Compatible Inputs Given Source Output + operationId: get_compatible_inputs_given_source_output + parameters: + - name: service_key + in: path + required: true + schema: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Service Key + - name: service_version + in: path + required: true + schema: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Service Version + - name: fromService + in: query + required: true + schema: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Fromservice + - name: fromVersion + in: query + required: true + schema: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Fromversion + - name: fromOutput + in: query + required: true + schema: + type: string + pattern: ^[-_a-zA-Z0-9]+$ + title: Fromoutput + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_Annotated_str__StringConstraints___' + /v0/catalog/services/{service_key}/{service_version}/outputs: + get: + tags: + - catalog + summary: List Service Outputs + operationId: list_service_outputs + parameters: + - name: service_key + in: path + required: true + schema: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Service Key + - name: service_version + in: path + required: true + schema: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Service Version + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_Annotated_str__StringConstraints___' + /v0/catalog/services/{service_key}/{service_version}/outputs/{output_key}: + get: + tags: + - catalog + summary: Get Service Output + operationId: get_service_output + parameters: + - name: service_key + in: path + required: true + schema: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Service Key + - name: service_version + in: path + required: true + schema: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Service Version + - name: output_key + in: path + required: true + schema: + type: string + pattern: ^[-_a-zA-Z0-9]+$ + title: Output Key + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_ServiceOutputGet__' + /v0/catalog/services/{service_key}/{service_version}/outputs:match: + get: + tags: + - catalog + summary: Get Compatible Outputs Given Target Input + operationId: get_compatible_outputs_given_target_input + parameters: + - name: service_key + in: path + required: true + schema: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Service Key + - name: service_version + in: path + required: true + schema: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Service Version + - name: toService + in: query + required: true + schema: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Toservice + - name: toVersion + in: query + required: true + schema: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Toversion + - name: toInput + in: query + required: true + schema: + type: string + pattern: ^[-_a-zA-Z0-9]+$ + title: Toinput + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_Annotated_str__StringConstraints___' + /v0/catalog/services/{service_key}/{service_version}/resources: + get: + tags: + - catalog + summary: Get Service Resources + operationId: get_service_resources + parameters: + - name: service_key + in: path + required: true + schema: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Service Key + - name: service_version + in: path + required: true + schema: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Service Version + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_dict_Annotated_str__StringConstraints___ImageResources__' + /v0/catalog/services/{service_key}/{service_version}/pricing-plan: + get: + tags: + - catalog + - pricing-plans + summary: Get Service Pricing Plan + description: Retrieve default pricing plan for provided service + operationId: get_service_pricing_plan + parameters: + - name: service_key + in: path + required: true + schema: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Service Key + - name: service_version + in: path + required: true + schema: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Service Version + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_ServicePricingPlanGet_' + /v0/catalog/services/{service_key}/{service_version}/tags: + get: + tags: + - catalog + - tags + summary: List Service Tags + operationId: list_service_tags + parameters: + - name: service_key + in: path + required: true + schema: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Service Key + - name: service_version + in: path + required: true + schema: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Service Version + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_TagGet__' + /v0/catalog/services/{service_key}/{service_version}/tags/{tag_id}:add: + post: + tags: + - catalog + - tags + summary: Add Service Tag + operationId: add_service_tag + parameters: + - name: service_key + in: path + required: true + schema: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Service Key + - name: service_version + in: path + required: true + schema: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Service Version + - name: tag_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Tag Id + minimum: 0 + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_CatalogServiceGet_' + /v0/catalog/services/{service_key}/{service_version}/tags/{tag_id}:remove: + post: + tags: + - catalog + - tags + summary: Remove Service Tag + operationId: remove_service_tag + parameters: + - name: service_key + in: path + required: true + schema: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Service Key + - name: service_version + in: path + required: true + schema: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Service Version + - name: tag_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Tag Id + minimum: 0 + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_CatalogServiceGet_' + /v0/computations/{project_id}: + get: + tags: + - computations + - projects + summary: Get Computation + operationId: get_computation + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_ComputationGet_' + /v0/computations/{project_id}:start: + post: + tags: + - computations + - projects + summary: Start Computation + operationId: start_computation + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ComputationStart' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope__ComputationStarted_' + '402': + description: Insufficient credits to run computation + '404': + description: Project/wallet/pricing details were not found + '406': + description: Cluster not found + '409': + description: Project already started + '422': + description: Configuration error + '503': + description: Service not available + /v0/computations/{project_id}:stop: + post: + tags: + - computations + - projects + summary: Stop Computation + operationId: stop_computation + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + responses: + '204': + description: Successful Response + /v0/projects/{project_id}:xport: + post: + tags: + - projects + - exporter + summary: Export Project + description: creates an archive of the project and downloads it + operationId: export_project + parameters: + - name: project_id + in: path + required: true + schema: + type: string + title: Project Id + responses: + '200': + description: Successful Response + /v0/folders: + post: + tags: + - folders + summary: Create Folder + operationId: create_folder + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/FolderCreateBodyParams' + responses: + '201': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_FolderGet_' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + get: + tags: + - folders + summary: List Folders + operationId: list_folders + parameters: + - name: filters + in: query + required: false + schema: + anyOf: + - type: string + contentMediaType: application/json + contentSchema: {} + - type: 'null' + title: Filters + - name: order_by + in: query + required: false + schema: + type: string + contentMediaType: application/json + contentSchema: {} + default: '{"field":"modified","direction":"desc"}' + title: Order By + - name: limit + in: query + required: false + schema: + type: integer + default: 20 + title: Limit + - name: offset + in: query + required: false + schema: + type: integer + default: 0 + title: Offset + - name: folder_id + in: query + required: false + schema: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Folder Id + - name: workspace_id + in: query + required: false + schema: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Workspace Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_FolderGet__' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + /v0/folders:search: + get: + tags: + - folders + summary: List Folders Full Search + operationId: list_folders_full_search + parameters: + - name: filters + in: query + required: false + schema: + anyOf: + - type: string + contentMediaType: application/json + contentSchema: {} + - type: 'null' + title: Filters + - name: order_by + in: query + required: false + schema: + type: string + contentMediaType: application/json + contentSchema: {} + default: '{"field":"modified","direction":"desc"}' + title: Order By + - name: limit + in: query + required: false + schema: + type: integer + default: 20 + title: Limit + - name: offset + in: query + required: false + schema: + type: integer + default: 0 + title: Offset + - name: text + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Text + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_FolderGet__' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + /v0/folders/{folder_id}: + get: + tags: + - folders + summary: Get Folder + operationId: get_folder + parameters: + - name: folder_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Folder Id + minimum: 0 + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_FolderGet_' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + put: + tags: + - folders + summary: Replace Folder + operationId: replace_folder + parameters: + - name: folder_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Folder Id + minimum: 0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/FolderReplaceBodyParams' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_FolderGet_' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + delete: + tags: + - folders + summary: Delete Folder + operationId: delete_folder + parameters: + - name: folder_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Folder Id + minimum: 0 + responses: + '204': + description: Successful Response + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + /v0/folders/{folder_id}/workspaces/{workspace_id}:move: + post: + tags: + - folders + - workspaces + summary: Move Folder To Workspace + description: Move folder to the workspace + operationId: move_folder_to_workspace + parameters: + - name: folder_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Folder Id + minimum: 0 + - name: workspace_id + in: path + required: true + schema: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Workspace Id + responses: + '204': + description: Successful Response + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + /v0/tasks: + get: + tags: + - long-running-tasks + summary: List Tasks + operationId: list_tasks + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_TaskGet__' + /v0/tasks/{task_id}: + get: + tags: + - long-running-tasks + summary: Get Task Status + operationId: get_task_status + parameters: + - name: task_id + in: path + required: true + schema: + type: string + title: Task Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_TaskStatus_' + delete: + tags: + - long-running-tasks + summary: Cancel And Delete Task + operationId: cancel_and_delete_task + parameters: + - name: task_id + in: path + required: true + schema: + type: string + title: Task Id + responses: + '204': + description: Successful Response + /v0/tasks/{task_id}/result: + get: + tags: + - long-running-tasks + summary: Get Task Result + operationId: get_task_result + parameters: + - name: task_id + in: path + required: true + schema: + type: string + title: Task Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: {} + /v0/catalog/licensed-items: + get: + tags: + - licenses + - catalog + summary: List Licensed Items + operationId: list_licensed_items + parameters: + - name: order_by + in: query + required: false + schema: + type: string + contentMediaType: application/json + contentSchema: {} + default: '{"field":"display_name","direction":"desc"}' + title: Order By + - name: limit + in: query + required: false + schema: + type: integer + default: 20 + title: Limit + - name: offset + in: query + required: false + schema: + type: integer + default: 0 + title: Offset + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Page_LicensedItemRestGet_' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request + /v0/catalog/licensed-items/{licensed_item_id}:purchase: + post: + tags: + - licenses + - catalog + summary: Purchase Licensed Item + operationId: purchase_licensed_item + parameters: + - name: licensed_item_id + in: path + required: true + schema: + type: string + format: uuid + title: Licensed Item Id + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/LicensedItemsBodyParams' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/LicensedItemPurchaseGet' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request + /v0/wallets/{wallet_id}/licensed-items-purchases: + get: + tags: + - licenses + - wallets + summary: List Wallet Licensed Items Purchases + operationId: list_wallet_licensed_items_purchases + parameters: + - name: wallet_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Wallet Id + minimum: 0 + - name: order_by + in: query + required: false + schema: + type: string + contentMediaType: application/json + contentSchema: {} + default: '{"field":"purchased_at","direction":"desc"}' + title: Order By + - name: limit + in: query + required: false + schema: + type: integer + default: 20 + title: Limit + - name: offset + in: query + required: false + schema: + type: integer + default: 0 + title: Offset + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Page_LicensedItemPurchaseGet_' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request + /v0/licensed-items-purchases/{licensed_item_purchase_id}: + get: + tags: + - licenses + summary: Get Licensed Item Purchase + operationId: get_licensed_item_purchase + parameters: + - name: licensed_item_purchase_id + in: path + required: true + schema: + type: string + format: uuid + title: Licensed Item Purchase Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_LicensedItemPurchaseGet_' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request + /v0/wallets/{wallet_id}/licensed-items-checkouts: + get: + tags: + - licenses + - wallets + summary: List Licensed Item Checkouts For Wallet + operationId: list_licensed_item_checkouts_for_wallet + parameters: + - name: wallet_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Wallet Id + minimum: 0 + - name: order_by + in: query + required: false + schema: + type: string + contentMediaType: application/json + contentSchema: {} + default: '{"field":"started_at","direction":"desc"}' + title: Order By + - name: limit + in: query + required: false + schema: + type: integer + default: 20 + title: Limit + - name: offset + in: query + required: false + schema: + type: integer + default: 0 + title: Offset + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Page_LicensedItemPurchaseGet_' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request + /v0/licensed-items-checkouts/{licensed_item_checkout_id}: + get: + tags: + - licenses + summary: Get Licensed Item Checkout + operationId: get_licensed_item_checkout + parameters: + - name: licensed_item_checkout_id + in: path + required: true + schema: + type: string + format: uuid + title: Licensed Item Checkout Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_LicensedItemPurchaseGet_' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request + /v0/services: + get: + tags: + - nih-sparc + summary: List Latest Services + description: Returns a list latest version of services + operationId: list_latest_services + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_ServiceGet__' + /v0/viewers: + get: + tags: + - nih-sparc + summary: List Viewers + description: 'Lists all publically available viewers + + + Notice that this might contain multiple services for the same filetype + + + If file_type is provided, then it filters viewer for that filetype' + operationId: list_viewers + parameters: + - name: file_type + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: File Type + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_Viewer__' + /v0/viewers/default: + get: + tags: + - nih-sparc + summary: List Default Viewers + description: 'Lists the default viewer for each supported filetype + + + This was interfaced as a subcollection of viewers because it is a very common + use-case + + + Only publicaly available viewers + + + If file_type is provided, then it filters viewer for that filetype' + operationId: list_default_viewers + parameters: + - name: file_type + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: File Type + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_Viewer__' + /view: + get: + tags: + - nih-sparc + summary: Get Redirection To Viewer + description: Opens a viewer in osparc for data in the NIH-sparc portal + operationId: get_redirection_to_viewer + parameters: + - name: file_type + in: query + required: true + schema: + type: string + title: File Type + - name: viewer_key + in: query + required: true + schema: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Viewer Key + - name: file_size + in: query + required: true + schema: + type: integer + exclusiveMinimum: true + title: File Size + minimum: 0 + - name: download_link + in: query + required: true + schema: + type: string + format: uri + minLength: 1 + maxLength: 2083 + title: Download Link + - name: file_name + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + default: unknown + title: File Name + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ServiceKeyVersion' + responses: + '302': + description: Opens osparc and starts viewer for selected data + /study/{id}: + get: + tags: + - nih-sparc + summary: Get Redirection To Study Page + description: Opens a study published in osparc + operationId: get_redirection_to_study_page + parameters: + - name: id + in: path + required: true + schema: + type: string + format: uuid + title: Id + responses: + '302': + description: Opens osparc and opens a copy of publised study + /v0/projects: + post: + tags: + - projects + summary: Create Project + description: 'Creates a new project or copies an existing one. NOTE: implemented + as a long running task, i.e. requires polling `status_href` (HTTP_200_OK) + to get status and `result_href` (HTTP_201_CREATED) to get created project' + operationId: create_project + parameters: + - name: x_simcore_user_agent + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + default: undefined + title: X Simcore User Agent + - name: x_simcore_parent_project_uuid + in: query + required: false + schema: + anyOf: + - type: string + format: uuid + - type: 'null' + title: X Simcore Parent Project Uuid + - name: x_simcore_parent_node_id + in: query + required: false + schema: + anyOf: + - type: string + format: uuid + - type: 'null' + title: X Simcore Parent Node Id + - name: from_study + in: query + required: false + schema: + anyOf: + - type: string + format: uuid + - type: 'null' + title: From Study + - name: as_template + in: query + required: false + schema: + type: boolean + default: false + title: As Template + - name: copy_data + in: query + required: false + schema: + type: boolean + default: true + title: Copy Data + - name: hidden + in: query + required: false + schema: + type: boolean + default: false + title: Hidden + requestBody: + required: true + content: + application/json: + schema: + anyOf: + - $ref: '#/components/schemas/ProjectCreateNew' + - $ref: '#/components/schemas/ProjectCopyOverride' + title: ' Body' + responses: + '202': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_TaskGet_' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + '422': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Unprocessable Entity + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + get: + tags: + - projects + summary: List Projects + operationId: list_projects + parameters: + - name: type + in: query + required: false + schema: + $ref: '#/components/schemas/ProjectTypeAPI' + default: all + - name: show_hidden + in: query + required: false + schema: + type: boolean + default: false + title: Show Hidden + - name: search + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Search + - name: folder_id + in: query + required: false + schema: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Folder Id + - name: workspace_id + in: query + required: false + schema: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Workspace Id + - name: filters + in: query + required: false + schema: + anyOf: + - type: string + contentMediaType: application/json + contentSchema: {} + - type: 'null' + title: Filters + - name: order_by + in: query + required: false + schema: + type: string + contentMediaType: application/json + contentSchema: {} + default: '{"field":"last_change_date","direction":"desc"}' + title: Order By + - name: limit + in: query + required: false + schema: + type: integer + default: 20 + title: Limit + - name: offset + in: query + required: false + schema: + type: integer + default: 0 + title: Offset + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Page_ProjectListItem_' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + '422': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Unprocessable Entity + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + /v0/projects/active: + get: + tags: + - projects + summary: Get Active Project + operationId: get_active_project + parameters: + - name: client_session_id + in: query + required: true + schema: + type: string + title: Client Session Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_ProjectGet_' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + '422': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Unprocessable Entity + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + /v0/projects/{project_id}: + get: + tags: + - projects + summary: Get Project + operationId: get_project + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_ProjectGet_' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + '422': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Unprocessable Entity + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + patch: + tags: + - projects + summary: Patch Project + operationId: patch_project + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectPatch' + responses: + '204': + description: Successful Response + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + '422': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Unprocessable Entity + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + delete: + tags: + - projects + summary: Delete Project + operationId: delete_project + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + responses: + '204': + description: Successful Response + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + '422': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Unprocessable Entity + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + /v0/projects/{project_id}:clone: + post: + tags: + - projects + summary: Clone Project + operationId: clone_project + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + responses: + '201': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_TaskGet_' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + '422': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Unprocessable Entity + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + /v0/projects:search: + get: + tags: + - projects + summary: List Projects Full Search + operationId: list_projects_full_search + parameters: + - name: filters + in: query + required: false + schema: + anyOf: + - type: string + contentMediaType: application/json + contentSchema: {} + - type: 'null' + title: Filters + - name: order_by + in: query + required: false + schema: + type: string + contentMediaType: application/json + contentSchema: {} + default: '{"field":"last_change_date","direction":"desc"}' + title: Order By + - name: limit + in: query + required: false + schema: + type: integer + default: 20 + title: Limit + - name: offset + in: query + required: false + schema: + type: integer + default: 0 + title: Offset + - name: text + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Text + - name: tag_ids + in: query + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: Tag Ids + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Page_ProjectListItem_' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + '422': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Unprocessable Entity + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + /v0/projects/{project_id}/inactivity: + get: + tags: + - projects + summary: Get Project Inactivity + operationId: get_project_inactivity + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_GetProjectInactivityResponse_' + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + '422': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Unprocessable Entity + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + /v0/projects/{project_uuid}/comments: + post: + tags: + - projects + - comments + summary: Create Project Comment + description: Create a new comment for a specific project. The request body should + contain the comment contents and user information. + operationId: create_project_comment + parameters: + - name: project_uuid + in: path + required: true + schema: + type: string + format: uuid + title: Project Uuid + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/_ProjectCommentsBodyParams' + responses: + '201': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_dict_Literal__comment_id____Annotated_int__Gt___' + get: + tags: + - projects + - comments + summary: List Project Comments + description: Retrieve all comments for a specific project. + operationId: list_project_comments + parameters: + - name: project_uuid + in: path + required: true + schema: + type: string + format: uuid + title: Project Uuid + - name: limit + in: query + required: false + schema: + type: integer + default: 20 + title: Limit + - name: offset + in: query + required: false + schema: + type: integer + minimum: 0 + default: 0 + title: Offset + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_ProjectsCommentsAPI__' + /v0/projects/{project_uuid}/comments/{comment_id}: + put: + tags: + - projects + - comments + summary: Update Project Comment + description: Update the contents of a specific comment for a project. The request + body should contain the updated comment contents. + operationId: update_project_comment + parameters: + - name: project_uuid + in: path + required: true + schema: + type: string + format: uuid + title: Project Uuid + - name: comment_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Comment Id + minimum: 0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/_ProjectCommentsBodyParams' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_ProjectsCommentsAPI_' + delete: + tags: + - projects + - comments + summary: Delete Project Comment + description: Delete a specific comment associated with a project. + operationId: delete_project_comment + parameters: + - name: project_uuid + in: path + required: true + schema: + type: string + format: uuid + title: Project Uuid + - name: comment_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Comment Id + minimum: 0 + responses: + '204': + description: Successful Response + get: + tags: + - projects + - comments + summary: Get Project Comment + description: Retrieve a specific comment by its ID within a project. + operationId: get_project_comment + parameters: + - name: project_uuid + in: path + required: true + schema: + type: string + format: uuid + title: Project Uuid + - name: comment_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Comment Id + minimum: 0 + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_ProjectsCommentsAPI_' + /v0/projects/{project_id}/folders/{folder_id}: + put: + tags: + - projects + - folders + summary: Replace Project Folder + description: Move project to the folder + operationId: replace_project_folder + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + - name: folder_id + in: path + required: true + schema: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Folder Id + responses: + '204': + description: Successful Response + /v0/projects/{project_id}/groups/{group_id}: + post: + tags: + - projects + - groups + summary: Create Project Group + operationId: create_project_group + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + - name: group_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Group Id + minimum: 0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/_ProjectsGroupsBodyParams' + responses: + '201': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_ProjectGroupGet_' + put: + tags: + - projects + - groups + summary: Replace Project Group + operationId: replace_project_group + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + - name: group_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Group Id + minimum: 0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/_ProjectsGroupsBodyParams' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_ProjectGroupGet_' + delete: + tags: + - projects + - groups + summary: Delete Project Group + operationId: delete_project_group + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + - name: group_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Group Id + minimum: 0 + responses: + '204': + description: Successful Response + /v0/projects/{project_id}/groups: + get: + tags: + - projects + - groups + summary: List Project Groups + operationId: list_project_groups + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_ProjectGroupGet__' + /v0/projects/{project_id}/metadata: + get: + tags: + - projects + - metadata + summary: Get Project Metadata + operationId: get_project_metadata + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_ProjectMetadataGet_' + patch: + tags: + - projects + - metadata + summary: Update Project Metadata + operationId: update_project_metadata + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ProjectMetadataUpdate' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_ProjectMetadataGet_' + /v0/projects/{project_id}/nodes: + post: + tags: + - projects + - nodes + summary: Create Node + operationId: create_node + parameters: + - name: project_id + in: path + required: true + schema: + type: string + title: Project Id + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NodeCreate' + responses: + '201': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_NodeCreated_' + /v0/projects/{project_id}/nodes/{node_id}: + get: + tags: + - projects + - nodes + summary: Get Node + operationId: get_node + parameters: + - name: project_id + in: path + required: true + schema: + type: string + title: Project Id + - name: node_id + in: path + required: true + schema: + type: string + title: Node Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_Union_NodeGetIdle__NodeGetUnknown__RunningDynamicServiceDetails__NodeGet__' + delete: + tags: + - projects + - nodes + summary: Delete Node + operationId: delete_node + parameters: + - name: project_id + in: path + required: true + schema: + type: string + title: Project Id + - name: node_id + in: path + required: true + schema: + type: string + title: Node Id + responses: + '204': + description: Successful Response + patch: + tags: + - projects + - nodes + summary: Patch Project Node + operationId: patch_project_node + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + - name: node_id + in: path + required: true + schema: + type: string + title: Node Id + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NodePatch' + responses: + '204': + description: Successful Response + /v0/projects/{project_id}/nodes/{node_id}:retrieve: + post: + tags: + - projects + - nodes + summary: Retrieve Node + operationId: retrieve_node + parameters: + - name: project_id + in: path + required: true + schema: + type: string + title: Project Id + - name: node_id + in: path + required: true + schema: + type: string + title: Node Id + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NodeRetrieve' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_NodeRetrieved_' + /v0/projects/{project_id}/nodes/{node_id}:start: + post: + tags: + - projects + - nodes + summary: Start Node + operationId: start_node + parameters: + - name: project_id + in: path + required: true + schema: + type: string + title: Project Id + - name: node_id + in: path + required: true + schema: + type: string + title: Node Id + responses: + '204': + description: Successful Response + /v0/projects/{project_id}/nodes/{node_id}:stop: + post: + tags: + - projects + - nodes + summary: Stop Node + operationId: stop_node + parameters: + - name: project_id + in: path + required: true + schema: + type: string + title: Project Id + - name: node_id + in: path + required: true + schema: + type: string + title: Node Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_TaskGet_' + /v0/projects/{project_id}/nodes/{node_id}:restart: + post: + tags: + - projects + - nodes + summary: Restart Node + description: Note that it has only effect on nodes associated to dynamic services + operationId: restart_node + parameters: + - name: project_id + in: path + required: true + schema: + type: string + title: Project Id + - name: node_id + in: path + required: true + schema: + type: string + title: Node Id + responses: + '204': + description: Successful Response + /v0/projects/{project_id}/nodes/{node_id}/outputs: + patch: + tags: + - projects + - nodes + summary: Update Node Outputs + operationId: update_node_outputs + parameters: + - name: project_id + in: path + required: true + schema: + type: string + title: Project Id + - name: node_id + in: path + required: true + schema: + type: string + title: Node Id + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NodeOutputs' + responses: + '204': + description: Successful Response + /v0/projects/{project_id}/nodes/{node_id}/resources: + get: + tags: + - projects + - nodes + summary: Get Node Resources + operationId: get_node_resources + parameters: + - name: project_id + in: path + required: true + schema: + type: string + title: Project Id + - name: node_id + in: path + required: true + schema: + type: string + title: Node Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_dict_Annotated_str__StringConstraints___ImageResources__' + put: + tags: + - projects + - nodes + summary: Replace Node Resources + operationId: replace_node_resources + parameters: + - name: project_id + in: path + required: true + schema: + type: string + title: Project Id + - name: node_id + in: path + required: true + schema: + type: string + title: Node Id + requestBody: + required: true + content: + application/json: + schema: + type: object + title: ' New' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_dict_Annotated_str__StringConstraints___ImageResources__' + /v0/projects/{project_id}/nodes/-/services:access: + get: + tags: + - projects + - nodes + summary: Get Project Services Access For Gid + description: Check whether provided group has access to the project services + operationId: get_project_services_access_for_gid + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + - name: for_gid + in: query + required: true + schema: + type: integer + exclusiveMinimum: true + title: For Gid + minimum: 0 + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope__ProjectGroupAccess_' + /v0/projects/{project_id}/nodes/-/preview: + get: + tags: + - projects + - nodes + summary: List Project Nodes Previews + description: Lists all previews in the node's project + operationId: list_project_nodes_previews + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list__ProjectNodePreview__' + /v0/projects/{project_id}/nodes/{node_id}/preview: + get: + tags: + - projects + - nodes + summary: Get Project Node Preview + description: Gets a give node's preview + operationId: get_project_node_preview + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + - name: node_id + in: path + required: true + schema: + type: string + format: uuid + title: Node Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope__ProjectNodePreview_' + '404': + description: Node has no preview + /v0/projects/{project_id}/nodes/{node_id}/pricing-unit: + get: + tags: + - projects + summary: Get Project Node Pricing Unit + description: Get currently connected pricing unit to the project node. + operationId: get_project_node_pricing_unit + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + - name: node_id + in: path + required: true + schema: + type: string + format: uuid + title: Node Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_Union_PricingUnitGet__NoneType__' + /v0/projects/{project_id}/nodes/{node_id}/pricing-plan/{pricing_plan_id}/pricing-unit/{pricing_unit_id}: + put: + tags: + - projects + summary: Connect Pricing Unit To Project Node + description: Connect pricing unit to the project node (Project node can have + only one pricing unit) + operationId: connect_pricing_unit_to_project_node + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + - name: node_id + in: path + required: true + schema: + type: string + format: uuid + title: Node Id + - name: pricing_plan_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Pricing Plan Id + minimum: 0 + - name: pricing_unit_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Pricing Unit Id + minimum: 0 + responses: + '204': + description: Successful Response + /v0/projects/{project_id}/inputs: + get: + tags: + - projects + - ports + summary: Get Project Inputs + description: New in version *0.10* + operationId: get_project_inputs + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_dict_UUID__ProjectInputGet__' + patch: + tags: + - projects + - ports + summary: Update Project Inputs + description: New in version *0.10* + operationId: update_project_inputs + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + requestBody: + required: true + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ProjectInputUpdate' + title: ' Updates' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_dict_UUID__ProjectInputGet__' + /v0/projects/{project_id}/outputs: + get: + tags: + - projects + - ports + summary: Get Project Outputs + description: New in version *0.10* + operationId: get_project_outputs + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_dict_UUID__ProjectOutputGet__' + /v0/projects/{project_id}/metadata/ports: + get: + tags: + - projects + - ports + summary: List Project Metadata Ports + description: New in version *0.12* + operationId: list_project_metadata_ports + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_ProjectMetadataPortGet__' + /v0/projects/{project_id}:open: + post: + tags: + - projects + summary: Open Project + operationId: open_project + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + - name: disable_service_auto_start + in: query + required: false + schema: + type: boolean + default: false + title: Disable Service Auto Start + requestBody: + required: true + content: + application/json: + schema: + type: string + title: Client Session Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_ProjectGet_' + '400': + description: ValidationError + '402': + description: WalletNotEnoughCreditsError + '403': + description: ProjectInvalidRightsError + '404': + description: ProjectNotFoundError, UserDefaultWalletNotFoundError + '409': + description: ProjectTooManyProjectOpenedError + '422': + description: ValidationError + '503': + description: DirectorServiceError + /v0/projects/{project_id}:close: + post: + tags: + - projects + summary: Close Project + operationId: close_project + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + requestBody: + required: true + content: + application/json: + schema: + type: string + title: Client Session Id + responses: + '204': + description: Successful Response + /v0/projects/{project_id}/state: + get: + tags: + - projects + summary: Get Project State + operationId: get_project_state + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_ProjectState_' + /v0/projects/{project_uuid}/tags/{tag_id}:add: + post: + tags: + - projects + - tags + summary: Add Project Tag + description: 'Links an existing label with an existing study + + + NOTE: that the tag is not created here' + operationId: add_project_tag + parameters: + - name: project_uuid + in: path + required: true + schema: + type: string + format: uuid + title: Project Uuid + - name: tag_id + in: path + required: true + schema: + type: integer + title: Tag Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_ProjectGet_' + /v0/projects/{project_uuid}/tags/{tag_id}:remove: + post: + tags: + - projects + - tags + summary: Remove Project Tag + description: 'Removes an existing link between a label and a study + + + NOTE: that the tag is not deleted here' + operationId: remove_project_tag + parameters: + - name: project_uuid + in: path + required: true + schema: + type: string + format: uuid + title: Project Uuid + - name: tag_id + in: path + required: true + schema: + type: integer + title: Tag Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_ProjectGet_' + /v0/projects/{project_id}/wallet: + get: + tags: + - projects + summary: Get Project Wallet + description: Get current connected wallet to the project. + operationId: get_project_wallet + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_Union_WalletGet__NoneType__' + /v0/projects/{project_id}/wallet/{wallet_id}: + put: + tags: + - projects + summary: Connect Wallet To Project + description: Connect wallet to the project (Project can have only one wallet) + operationId: connect_wallet_to_project + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + - name: wallet_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Wallet Id + minimum: 0 + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_WalletGet_' + /v0/projects/{project_id}/wallet/{wallet_id}:pay-debt: + post: + tags: + - projects + summary: Pay Project Debt + operationId: pay_project_debt + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + - name: wallet_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Wallet Id + minimum: 0 + - name: amount + in: query + required: true + schema: + anyOf: + - type: number + exclusiveMaximum: true + maximum: 0.0 + - type: string + title: Amount + responses: + '204': + description: Successful Response + /v0/projects/{project_id}/workspaces/{workspace_id}:move: + post: + tags: + - projects + - workspaces + summary: Move Project To Workspace + description: Move project to the workspace + operationId: move_project_to_workspace + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + - name: workspace_id + in: path + required: true + schema: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Workspace Id + responses: + '204': + description: Successful Response + /v0/publications/service-submission: + post: + tags: + - publication + summary: Service Submission + description: Submits files with new service candidate + operationId: service_submission + requestBody: + content: + multipart/form-data: + schema: + $ref: '#/components/schemas/Body_service_submission' + required: true + responses: + '204': + description: Successful Response + /v0/services/-/resource-usages: + get: + tags: + - usage + summary: List Resource Usage Services + description: Retrieve finished and currently running user services (user and + product are taken from context, optionally wallet_id parameter might be provided). + operationId: list_resource_usage_services + parameters: + - name: order_by + in: query + required: false + schema: + type: string + contentMediaType: application/json + contentSchema: {} + default: '{"field":"started_at","direction":"desc"}' + title: Order By + - name: wallet_id + in: query + required: false + schema: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Wallet Id + - name: filters + in: query + required: false + schema: + anyOf: + - type: string + contentMediaType: application/json + contentSchema: {} + - type: 'null' + title: Filters + - name: limit + in: query + required: false + schema: + type: integer + default: 20 + title: Limit + - name: offset + in: query + required: false + schema: + type: integer + default: 0 + title: Offset + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Page_ServiceRunGet_' + /v0/services/-/aggregated-usages: + get: + tags: + - usage + summary: List Osparc Credits Aggregated Usages + description: Used credits based on aggregate by type, currently supported `services`. + (user and product are taken from context, optionally wallet_id parameter might + be provided). + operationId: list_osparc_credits_aggregated_usages + parameters: + - name: limit + in: query + required: false + schema: + type: integer + default: 20 + title: Limit + - name: offset + in: query + required: false + schema: + type: integer + default: 0 + title: Offset + - name: aggregated_by + in: query + required: true + schema: + $ref: '#/components/schemas/ServicesAggregatedUsagesType' + - name: time_period + in: query + required: true + schema: + $ref: '#/components/schemas/ServicesAggregatedUsagesTimePeriod' + - name: wallet_id + in: query + required: true + schema: + type: integer + title: Wallet Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Page_OsparcCreditsAggregatedByServiceGet_' + /v0/services/-/usage-report: + get: + tags: + - usage + summary: Export Resource Usage Services + description: Redirects to download CSV link. CSV obtains finished and currently + running user services (user and product are taken from context, optionally + wallet_id parameter might be provided). + operationId: export_resource_usage_services + parameters: + - name: order_by + in: query + required: false + schema: + type: string + contentMediaType: application/json + contentSchema: {} + default: '{"field":"started_at","direction":"desc"}' + title: Order By + - name: wallet_id + in: query + required: false + schema: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Wallet Id + - name: filters + in: query + required: false + schema: + anyOf: + - type: string + contentMediaType: application/json + contentSchema: {} + - type: 'null' + title: Filters + responses: + '302': + description: redirection to download link + content: + application/json: + schema: {} + /v0/pricing-plans/{pricing_plan_id}/pricing-units/{pricing_unit_id}: + get: + tags: + - pricing-plans + summary: Get Pricing Plan Unit + operationId: get_pricing_plan_unit + parameters: + - name: pricing_plan_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Pricing Plan Id + minimum: 0 + - name: pricing_unit_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Pricing Unit Id + minimum: 0 + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_PricingUnitGet_' + /v0/pricing-plans: + get: + tags: + - pricing-plans + summary: List Pricing Plans + description: To keep the listing lightweight, the pricingUnits field is None. + operationId: list_pricing_plans + parameters: + - name: limit + in: query + required: false + schema: + type: integer + default: 20 + title: Limit + - name: offset + in: query + required: false + schema: + type: integer + default: 0 + title: Offset + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Page_PricingPlanGet_' + /v0/pricing-plans/{pricing_plan_id}: + get: + tags: + - pricing-plans + summary: Get Pricing Plan + operationId: get_pricing_plan + parameters: + - name: pricing_plan_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Pricing Plan Id + minimum: 0 + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_PricingPlanGet_' + /v0/admin/pricing-plans: + get: + tags: + - admin + summary: List Pricing Plans For Admin User + description: To keep the listing lightweight, the pricingUnits field is None. + operationId: list_pricing_plans_for_admin_user + parameters: + - name: limit + in: query + required: false + schema: + type: integer + default: 20 + title: Limit + - name: offset + in: query + required: false + schema: + type: integer + default: 0 + title: Offset + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Page_PricingPlanAdminGet_' + post: + tags: + - admin + summary: Create Pricing Plan + operationId: create_pricing_plan + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreatePricingPlanBodyParams' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_PricingPlanAdminGet_' + /v0/admin/pricing-plans/{pricing_plan_id}: + get: + tags: + - admin + summary: Get Pricing Plan For Admin User + operationId: get_pricing_plan_for_admin_user + parameters: + - name: pricing_plan_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Pricing Plan Id + minimum: 0 + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_PricingPlanAdminGet_' + put: + tags: + - admin + summary: Update Pricing Plan + operationId: update_pricing_plan + parameters: + - name: pricing_plan_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Pricing Plan Id + minimum: 0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdatePricingPlanBodyParams' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_PricingPlanAdminGet_' + /v0/admin/pricing-plans/{pricing_plan_id}/pricing-units/{pricing_unit_id}: + get: + tags: + - admin + summary: Get Pricing Unit + operationId: get_pricing_unit + parameters: + - name: pricing_plan_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Pricing Plan Id + minimum: 0 + - name: pricing_unit_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Pricing Unit Id + minimum: 0 + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_PricingUnitAdminGet_' + put: + tags: + - admin + summary: Update Pricing Unit + operationId: update_pricing_unit + parameters: + - name: pricing_plan_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Pricing Plan Id + minimum: 0 + - name: pricing_unit_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Pricing Unit Id + minimum: 0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdatePricingUnitBodyParams' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_PricingUnitAdminGet_' + /v0/admin/pricing-plans/{pricing_plan_id}/pricing-units: + post: + tags: + - admin + summary: Create Pricing Unit + operationId: create_pricing_unit + parameters: + - name: pricing_plan_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Pricing Plan Id + minimum: 0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreatePricingUnitBodyParams' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_PricingUnitAdminGet_' + /v0/admin/pricing-plans/{pricing_plan_id}/billable-services: + get: + tags: + - admin + summary: List Connected Services To Pricing Plan + operationId: list_connected_services_to_pricing_plan + parameters: + - name: pricing_plan_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Pricing Plan Id + minimum: 0 + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_PricingPlanToServiceAdminGet__' + post: + tags: + - admin + summary: Connect Service To Pricing Plan + operationId: connect_service_to_pricing_plan + parameters: + - name: pricing_plan_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Pricing Plan Id + minimum: 0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ConnectServiceToPricingPlanBodyParams' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_PricingPlanToServiceAdminGet_' + /: + get: + tags: + - statics + summary: Get Cached Frontend Index + operationId: get_cached_frontend_index + responses: + '200': + description: Successful Response + content: + text/html: + schema: + type: string + /static-frontend-data.json: + get: + tags: + - statics + summary: Static Frontend Data + description: Generic static info on the product's app + operationId: static_frontend_data + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/StaticFrontEndDict' + /v0/storage/locations: + get: + tags: + - storage + summary: List Storage Locations + description: Get available storage locations + operationId: list_storage_locations + responses: + '200': + description: Successful Response + content: + application/json: + schema: + items: + $ref: '#/components/schemas/FileLocation' + type: array + title: Response List Storage Locations + /v0/storage/locations/{location_id}/datasets: + get: + tags: + - storage + summary: List Datasets Metadata + description: Get datasets metadata + operationId: list_datasets_metadata + parameters: + - name: location_id + in: path + required: true + schema: + type: integer + title: Location Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_DatasetMetaData__' + /v0/storage/locations/{location_id}/files/metadata: + get: + tags: + - storage + summary: Get Files Metadata + description: Get datasets metadata + operationId: get_files_metadata + parameters: + - name: location_id + in: path + required: true + schema: + type: integer + title: Location Id + - name: uuid_filter + in: query + required: false + schema: + type: string + default: '' + title: Uuid Filter + - name: expand_dirs + in: query + required: false + schema: + type: boolean + description: Automatic directory expansion. This will be replaced by pagination + the future + default: true + title: Expand Dirs + description: Automatic directory expansion. This will be replaced by pagination + the future + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_DatasetMetaData__' + /v0/storage/locations/{location_id}/datasets/{dataset_id}/metadata: + get: + tags: + - storage + summary: List Dataset Files Metadata + description: Get Files Metadata + operationId: list_dataset_files_metadata + parameters: + - name: location_id + in: path + required: true + schema: + type: integer + title: Location Id + - name: dataset_id + in: path + required: true + schema: + type: string + title: Dataset Id + - name: expand_dirs + in: query + required: false + schema: + type: boolean + description: Automatic directory expansion. This will be replaced by pagination + the future + default: true + title: Expand Dirs + description: Automatic directory expansion. This will be replaced by pagination + the future + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_FileMetaDataGet__' + /v0/storage/locations/{location_id}/files/{file_id}/metadata: + get: + tags: + - storage + summary: Get File Metadata + description: Get File Metadata + operationId: get_file_metadata + parameters: + - name: location_id + in: path + required: true + schema: + type: integer + title: Location Id + - name: file_id + in: path + required: true + schema: + type: string + title: File Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + anyOf: + - $ref: '#/components/schemas/FileMetaData' + - $ref: '#/components/schemas/Envelope_FileMetaDataGet_' + title: Response Get File Metadata + /v0/storage/locations/{location_id}/files/{file_id}: + get: + tags: + - storage + summary: Download File + description: Returns download link for requested file + operationId: download_file + parameters: + - name: location_id + in: path + required: true + schema: + type: integer + title: Location Id + - name: file_id + in: path + required: true + schema: + type: string + title: File Id + - name: link_type + in: query + required: false + schema: + $ref: '#/components/schemas/LinkType' + default: PRESIGNED + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_PresignedLink_' + put: + tags: + - storage + summary: Upload File + description: Returns upload link + operationId: upload_file + parameters: + - name: location_id + in: path + required: true + schema: + type: integer + title: Location Id + - name: file_id + in: path + required: true + schema: + type: string + title: File Id + - name: file_size + in: query + required: true + schema: + anyOf: + - type: string + pattern: ^\s*(\d*\.?\d+)\s*(\w+)? + - type: integer + minimum: 0 + - type: 'null' + title: File Size + - name: link_type + in: query + required: false + schema: + $ref: '#/components/schemas/LinkType' + default: PRESIGNED + - name: is_directory + in: query + required: false + schema: + type: boolean + default: false + title: Is Directory + responses: + '200': + description: Successful Response + content: + application/json: + schema: + anyOf: + - $ref: '#/components/schemas/Envelope_FileUploadSchema_' + - $ref: '#/components/schemas/Envelope_AnyUrl_' + title: Response Upload File + delete: + tags: + - storage + summary: Delete File + description: Deletes File + operationId: delete_file + parameters: + - name: location_id + in: path + required: true + schema: + type: integer + title: Location Id + - name: file_id + in: path + required: true + schema: + type: string + title: File Id + responses: + '204': + description: Successful Response + /v0/storage/locations/{location_id}/files/{file_id}:abort: + post: + tags: + - storage + summary: Abort Upload File + description: 'aborts an upload if user has the rights to, and reverts + + to the latest version if available, else will delete the file' + operationId: abort_upload_file + parameters: + - name: location_id + in: path + required: true + schema: + type: integer + title: Location Id + - name: file_id + in: path + required: true + schema: + type: string + title: File Id + responses: + '204': + description: Successful Response + /v0/storage/locations/{location_id}/files/{file_id}:complete: + post: + tags: + - storage + summary: Complete Upload File + description: completes an upload if the user has the rights to + operationId: complete_upload_file + parameters: + - name: location_id + in: path + required: true + schema: + type: integer + title: Location Id + - name: file_id + in: path + required: true + schema: + type: string + title: File Id + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_FileUploadCompletionBody_' + responses: + '202': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_FileUploadCompleteResponse_' + /v0/storage/locations/{location_id}/files/{file_id}:complete/futures/{future_id}: + post: + tags: + - storage + summary: Is Completed Upload File + description: Check for upload completion + operationId: is_completed_upload_file + parameters: + - name: location_id + in: path + required: true + schema: + type: integer + title: Location Id + - name: file_id + in: path + required: true + schema: + type: string + title: File Id + - name: future_id + in: path + required: true + schema: + type: string + title: Future Id + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_FileUploadCompleteFutureResponse_' + /v0/storage/export-data: + post: + tags: + - storage + summary: Storage Export Data + description: Export data + operationId: export_data + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DataExportPost' + required: true + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_AsyncJobGet_' + /v0/storage/async-jobs/status: + get: + tags: + - storage + summary: Storage Async Job Status + description: Get async job status + operationId: get_async_job_status + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AsyncJobGet' + required: true + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_AsyncJobStatus_' + /v0/trash:empty: + post: + tags: + - trash + summary: Empty Trash + operationId: empty_trash + responses: + '204': + description: Successful Response + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + '409': + description: Conflict + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + '503': + description: Service Unavailable + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + '422': + description: Unprocessable Entity + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + '402': + description: Payment Required + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + /v0/projects/{project_id}:trash: + post: + tags: + - trash + - projects + summary: Trash Project + operationId: trash_project + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + - name: force + in: query + required: false + schema: + type: boolean + default: false + title: Force + responses: + '204': + description: Successful Response + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '404': + description: Not such a project + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + '409': + description: Project is in use and cannot be trashed + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + '503': + description: Trash service error + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + '422': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Unprocessable Entity + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + /v0/projects/{project_id}:untrash: + post: + tags: + - trash + - projects + summary: Untrash Project + operationId: untrash_project + parameters: + - name: project_id + in: path + required: true + schema: + type: string + format: uuid + title: Project Id + responses: + '204': + description: Successful Response + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + '422': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Unprocessable Entity + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + /v0/folders/{folder_id}:trash: + post: + tags: + - trash + - folders + summary: Trash Folder + operationId: trash_folder + parameters: + - name: folder_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Folder Id + minimum: 0 + - name: force + in: query + required: false + schema: + type: boolean + default: false + title: Force + responses: + '204': + description: Successful Response + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '404': + description: Not such a folder + '409': + description: One or more projects in the folder are in use and cannot be + trashed + '503': + description: Trash service error + '422': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Unprocessable Entity + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + /v0/folders/{folder_id}:untrash: + post: + tags: + - trash + - folders + summary: Untrash Folder + operationId: untrash_folder + parameters: + - name: folder_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Folder Id + minimum: 0 + responses: + '204': + description: Successful Response + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + '422': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Unprocessable Entity + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + /v0/workspaces/{workspace_id}:trash: + post: + tags: + - trash + - workspaces + summary: Trash Workspace + operationId: trash_workspace + parameters: + - name: workspace_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Workspace Id + minimum: 0 + - name: force + in: query + required: false + schema: + type: boolean + default: false + title: Force + responses: + '204': + description: Successful Response + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '404': + description: Not such a workspace + '409': + description: One or more projects in the workspace are in use and cannot + be trashed + '503': + description: Trash service error + '422': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Unprocessable Entity + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + /v0/workspaces/{workspace_id}:untrash: + post: + tags: + - trash + - workspaces + summary: Untrash Workspace + operationId: untrash_workspace + parameters: + - name: workspace_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Workspace Id + minimum: 0 + responses: + '204': + description: Successful Response + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + '422': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Unprocessable Entity + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Bad Request + '402': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Payment Required + /v0/workspaces: + post: + tags: + - workspaces + summary: Create Workspace + operationId: create_workspace + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WorkspaceCreateBodyParams' + responses: + '201': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_WorkspaceGet_' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + get: + tags: + - workspaces + summary: List Workspaces + operationId: list_workspaces + parameters: + - name: order_by + in: query + required: false + schema: + type: string + contentMediaType: application/json + contentSchema: {} + default: '{"field":"modified","direction":"desc"}' + title: Order By + - name: filters + in: query + required: false + schema: + anyOf: + - type: string + contentMediaType: application/json + contentSchema: {} + - type: 'null' + title: Filters + - name: limit + in: query + required: false + schema: + type: integer + default: 20 + title: Limit + - name: offset + in: query + required: false + schema: + type: integer + default: 0 + title: Offset + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_WorkspaceGet__' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + /v0/workspaces/{workspace_id}: + get: + tags: + - workspaces + summary: Get Workspace + operationId: get_workspace + parameters: + - name: workspace_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Workspace Id + minimum: 0 + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_WorkspaceGet_' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + put: + tags: + - workspaces + summary: Replace Workspace + operationId: replace_workspace + parameters: + - name: workspace_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Workspace Id + minimum: 0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WorkspaceReplaceBodyParams' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_WorkspaceGet_' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + delete: + tags: + - workspaces + summary: Delete Workspace + operationId: delete_workspace + parameters: + - name: workspace_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Workspace Id + minimum: 0 + responses: + '204': + description: Successful Response + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + /v0/workspaces/{workspace_id}/groups/{group_id}: + post: + tags: + - workspaces + - groups + summary: Create Workspace Group + operationId: create_workspace_group + parameters: + - name: workspace_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Workspace Id + minimum: 0 + - name: group_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Group Id + minimum: 0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WorkspacesGroupsBodyParams' + responses: + '201': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_WorkspaceGroupGet_' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + put: + tags: + - workspaces + - groups + summary: Replace Workspace Group + operationId: replace_workspace_group + parameters: + - name: workspace_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Workspace Id + minimum: 0 + - name: group_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Group Id + minimum: 0 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/WorkspacesGroupsBodyParams' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_WorkspaceGroupGet_' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + delete: + tags: + - workspaces + - groups + summary: Delete Workspace Group + operationId: delete_workspace_group + parameters: + - name: workspace_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Workspace Id + minimum: 0 + - name: group_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Group Id + minimum: 0 + responses: + '204': + description: Successful Response + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + /v0/workspaces/{workspace_id}/groups: + get: + tags: + - workspaces + - groups + summary: List Workspace Groups + operationId: list_workspace_groups + parameters: + - name: workspace_id + in: path + required: true + schema: + type: integer + exclusiveMinimum: true + title: Workspace Id + minimum: 0 + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_WorkspaceGroupGet__' + '404': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Not Found + '403': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Forbidden + '409': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Conflict + '503': + content: + application/json: + schema: + $ref: '#/components/schemas/EnvelopedError' + description: Service Unavailable + /v0/email:test: + post: + tags: + - admin + summary: Test Email + operationId: test_email + parameters: + - name: x-simcore-products-name + in: header + required: false + schema: + anyOf: + - type: string + - type: 'null' + title: X-Simcore-Products-Name + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TestEmail' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_Union_EmailTestFailed__EmailTestPassed__' + /v0/: + get: + tags: + - maintenance + summary: Healthcheck Readiness Probe + description: 'Readiness probe: check if the container is ready to receive traffic' + operationId: healthcheck_readiness_probe + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_HealthInfoDict_' + /v0/health: + get: + tags: + - maintenance + summary: Healthcheck Liveness Probe + description: 'Liveness probe: check if the container is alive' + operationId: healthcheck_liveness_probe + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_dict_str__Any__' + /v0/config: + get: + tags: + - maintenance + summary: Get Config + description: Front end runtime configuration + operationId: get_config + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_dict_str__Any__' + /v0/scheduled_maintenance: + get: + tags: + - maintenance + summary: Get Scheduled Maintenance + operationId: get_scheduled_maintenance + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_str_' + /v0/status: + get: + tags: + - maintenance + summary: Get App Status + description: checks status of self and connected services + operationId: get_app_status + responses: + '200': + description: Returns app status check + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_AppStatusCheck_' + /v0/status/diagnostics: + get: + tags: + - maintenance + summary: Get App Diagnostics + operationId: get_app_diagnostics + parameters: + - name: top_tracemalloc + in: query + required: false + schema: + anyOf: + - type: integer + - type: 'null' + title: Top Tracemalloc + responses: + '200': + description: Returns app diagnostics report + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_StatusDiagnosticsGet_' + /v0/status/{service_name}: + get: + tags: + - maintenance + summary: Get Service Status + operationId: get_service_status + parameters: + - name: service_name + in: path + required: true + schema: + type: string + title: Service Name + responses: + '200': + description: Returns app status check + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_AppStatusCheck_' +components: + schemas: + AccessEnum: + type: string + enum: + - ReadAndWrite + - Invisible + - ReadOnly + title: AccessEnum + AccessRights: + properties: + read: + type: boolean + title: Read + description: has read access + write: + type: boolean + title: Write + description: has write access + delete: + type: boolean + title: Delete + description: has deletion rights + additionalProperties: false + type: object + required: + - read + - write + - delete + title: AccessRights + AccountRequestInfo: + properties: + form: + type: object + title: Form + captcha: + type: string + title: Captcha + type: object + required: + - form + - captcha + title: AccountRequestInfo + example: + captcha: A12B34 + form: + address: Infinite Loop + application: Antenna_Design + city: Washington + company: EM Com + country: USA + description: Description of something + email: maxwel@email.com + eula: true + firstName: James + hear: Search_Engine + lastName: Maxwel + phone: +1 123456789 + postalCode: '98001' + privacyPolicy: true + Activity: + properties: + stats: + $ref: '#/components/schemas/Stats' + limits: + $ref: '#/components/schemas/Limits' + queued: + anyOf: + - type: boolean + - type: 'null' + title: Queued + type: object + required: + - stats + - limits + title: Activity + AnnotationUI: + properties: + type: + type: string + enum: + - note + - rect + - text + title: Type + color: + type: string + format: color + title: Color + attributes: + type: object + title: Attributes + description: svg attributes + additionalProperties: false + type: object + required: + - type + - color + - attributes + title: AnnotationUI + Announcement: + properties: + id: + type: string + title: Id + products: + items: + type: string + type: array + title: Products + start: + type: string + format: date-time + title: Start + end: + type: string + format: date-time + title: End + title: + type: string + title: Title + description: + type: string + title: Description + link: + type: string + title: Link + widgets: + items: + type: string + enum: + - login + - ribbon + - user-menu + type: array + title: Widgets + type: object + required: + - id + - products + - start + - end + - title + - description + - link + - widgets + title: Announcement + ApiKeyCreateRequest: + properties: + displayName: + type: string + minLength: 3 + title: Displayname + expiration: + anyOf: + - type: string + format: duration + - type: 'null' + title: Expiration + description: Time delta from creation time to expiration. If None, then + it does not expire. + type: object + required: + - displayName + title: ApiKeyCreateRequest + ApiKeyCreateResponse: + properties: + id: + type: string + maxLength: 100 + minLength: 1 + title: Id + displayName: + type: string + minLength: 3 + title: Displayname + expiration: + anyOf: + - type: string + format: duration + - type: 'null' + title: Expiration + description: Time delta from creation time to expiration. If None, then + it does not expire. + apiBaseUrl: + type: string + title: Apibaseurl + apiKey: + type: string + title: Apikey + apiSecret: + type: string + title: Apisecret + type: object + required: + - id + - displayName + - apiBaseUrl + - apiKey + - apiSecret + title: ApiKeyCreateResponse + ApiKeyGet: + properties: + id: + type: string + maxLength: 100 + minLength: 1 + title: Id + displayName: + type: string + minLength: 3 + title: Displayname + type: object + required: + - id + - displayName + title: ApiKeyGet + AppStatusCheck: + properties: + app_name: + type: string + title: App Name + description: Application name + version: + type: string + title: Version + description: Application's version + services: + type: object + title: Services + description: Other backend services connected from this service + default: {} + sessions: + anyOf: + - type: object + - type: 'null' + title: Sessions + description: Client sessions info. If single session per app, then is denoted + as main + default: {} + url: + anyOf: + - type: string + - type: 'null' + title: Url + description: Link to current resource + diagnostics_url: + anyOf: + - type: string + - type: 'null' + title: Diagnostics Url + description: Link to diagnostics report sub-resource. This MIGHT take some + time to compute + type: object + required: + - app_name + - version + title: AppStatusCheck + AsyncJobGet: + properties: + job_id: + type: string + format: uuid + title: Job Id + type: object + required: + - job_id + title: AsyncJobGet + AsyncJobStatus: + properties: + job_id: + type: string + format: uuid + title: Job Id + task_progress: + type: number + maximum: 1.0 + minimum: 0.0 + exclusiveMinimum: true + title: Task Progress + done: + type: boolean + title: Done + started: + type: string + format: date-time + title: Started + stopped: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Stopped + type: object + required: + - job_id + - task_progress + - done + - started + - stopped + title: AsyncJobStatus + Author: + properties: + name: + type: string + title: Name + description: Name of the author + email: + type: string + format: email + title: Email + description: Email address + affiliation: + anyOf: + - type: string + - type: 'null' + title: Affiliation + type: object + required: + - name + - email + title: Author + Body_service_submission: + properties: + file: + type: string + format: binary + title: File + description: metadata.json submission file + type: object + required: + - file + title: Body_service_submission + BootChoice: + properties: + label: + type: string + title: Label + description: + type: string + title: Description + type: object + required: + - label + - description + title: BootChoice + BootMode: + type: string + enum: + - CPU + - GPU + - MPI + title: BootMode + BootOption: + properties: + label: + type: string + title: Label + description: + type: string + title: Description + default: + type: string + title: Default + items: + additionalProperties: + $ref: '#/components/schemas/BootChoice' + type: object + title: Items + type: object + required: + - label + - description + - default + - items + title: BootOption + CatalogServiceGet: + properties: + key: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Key + version: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Version + name: + type: string + title: Name + thumbnail: + anyOf: + - type: string + - type: 'null' + title: Thumbnail + icon: + anyOf: + - type: string + - type: 'null' + title: Icon + description: + type: string + title: Description + descriptionUi: + type: boolean + title: Descriptionui + default: false + versionDisplay: + anyOf: + - type: string + - type: 'null' + title: Versiondisplay + type: + $ref: '#/components/schemas/ServiceType' + contact: + anyOf: + - type: string + format: email + - type: 'null' + title: Contact + authors: + items: + $ref: '#/components/schemas/Author' + type: array + minItems: 1 + title: Authors + owner: + anyOf: + - type: string + format: email + - type: 'null' + title: Owner + description: None when the owner email cannot be found in the database + inputs: + type: object + title: Inputs + description: inputs with extended information + outputs: + type: object + title: Outputs + description: outputs with extended information + bootOptions: + anyOf: + - type: object + - type: 'null' + title: Bootoptions + minVisibleInputs: + anyOf: + - type: integer + minimum: 0 + - type: 'null' + title: Minvisibleinputs + accessRights: + anyOf: + - additionalProperties: + $ref: '#/components/schemas/ServiceGroupAccessRightsV2' + type: object + - type: 'null' + title: Accessrights + classifiers: + anyOf: + - items: + type: string + type: array + - type: 'null' + title: Classifiers + default: [] + quality: + type: object + title: Quality + default: {} + history: + items: + $ref: '#/components/schemas/ServiceRelease' + type: array + title: History + description: history of releases for this service at this point in time, + starting from the newest to the oldest. It includes current release. + default: [] + type: object + required: + - key + - version + - name + - description + - type + - contact + - authors + - owner + - inputs + - outputs + - accessRights + title: CatalogServiceGet + example: + accessRights: + '1': + execute: true + write: false + authors: + - affiliation: ACME + email: author@acme.com + name: Author Bar + classifiers: [] + contact: contact@acme.com + description: A service which awaits for time to pass, two times. + description_ui: true + history: + - released: '2024-07-20T15:00:00' + version: 2.2.1 + version_display: Summer Release + - compatibility: + canUpdateTo: + version: 2.2.1 + version: 2.0.0 + - version: 0.9.11 + - version: 0.9.10 + - compatibility: + canUpdateTo: + version: 0.9.11 + version: 0.9.8 + - compatibility: + can_update_to: + version: 0.9.11 + released: '2024-01-20T18:49:17' + version: 0.9.1 + versionDisplay: Matterhorn + - retired: '2024-07-20T15:00:00' + version: 0.9.0 + - version: 0.8.0 + - version: 0.1.0 + icon: https://cdn-icons-png.flaticon.com/512/25/25231.png + inputs: + input0: + contentSchema: + title: Acceleration + type: number + x_unit: m/s**2 + description: acceleration with units + keyId: input_1 + label: Acceleration + type: ref_contentSchema + unitLong: meter/second3 + unitShort: m/s3 + key: simcore/services/comp/itis/sleeper + name: sleeper + outputs: + outFile: + description: Time the service waited before completion + displayOrder: 2 + keyId: output_2 + label: Time Slept + type: number + unit: second + unitLong: seconds + unitShort: sec + owner: owner@acme.com + quality: {} + type: computational + version: 2.2.1 + version_display: 2 Xtreme + CatalogServiceUpdate: + properties: + name: + anyOf: + - type: string + - type: 'null' + title: Name + thumbnail: + anyOf: + - type: string + maxLength: 2083 + minLength: 1 + format: uri + - type: 'null' + title: Thumbnail + icon: + anyOf: + - type: string + maxLength: 2083 + minLength: 1 + format: uri + - type: 'null' + title: Icon + description: + anyOf: + - type: string + - type: 'null' + title: Description + descriptionUi: + type: boolean + title: Descriptionui + default: false + versionDisplay: + anyOf: + - type: string + - type: 'null' + title: Versiondisplay + deprecated: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Deprecated + classifiers: + anyOf: + - items: + type: string + type: array + - type: 'null' + title: Classifiers + quality: + type: object + title: Quality + default: {} + accessRights: + anyOf: + - additionalProperties: + $ref: '#/components/schemas/ServiceGroupAccessRightsV2' + type: object + - type: 'null' + title: Accessrights + type: object + title: CatalogServiceUpdate + ChangePasswordBody: + properties: + current: + type: string + format: password + title: Current + writeOnly: true + new: + type: string + format: password + title: New + writeOnly: true + confirm: + type: string + format: password + title: Confirm + writeOnly: true + additionalProperties: false + type: object + required: + - current + - new + - confirm + title: ChangePasswordBody + CodePageParams: + properties: + message: + type: string + title: Message + expiration_2fa: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Expiration 2Fa + next_url: + anyOf: + - type: string + - type: 'null' + title: Next Url + type: object + required: + - message + title: CodePageParams + Compatibility: + properties: + canUpdateTo: + $ref: '#/components/schemas/CompatibleService' + description: Latest compatible service at this moment + type: object + required: + - canUpdateTo + title: Compatibility + CompatibleService: + properties: + key: + anyOf: + - type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + - type: 'null' + title: Key + description: If None, it refer to current service. Used only for inter-service + compatibility + version: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Version + type: object + required: + - version + title: CompatibleService + ComputationGet: + properties: + id: + type: string + format: uuid + title: Id + description: the id of the computation task + state: + $ref: '#/components/schemas/RunningState' + description: the state of the computational task + result: + anyOf: + - type: string + - type: 'null' + title: Result + description: the result of the computational task + pipeline_details: + $ref: '#/components/schemas/PipelineDetails' + description: the details of the generated pipeline + iteration: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Iteration + description: the iteration id of the computation task (none if no task ran + yet) + started: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Started + description: the timestamp when the computation was started or None if not + started yet + stopped: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Stopped + description: the timestamp when the computation was stopped or None if not + started nor stopped yet + submitted: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Submitted + description: task last modification timestamp or None if the there is no + task + url: + type: string + title: Url + description: the link where to get the status of the task + stop_url: + anyOf: + - type: string + - type: 'null' + title: Stop Url + description: the link where to stop the task + type: object + required: + - id + - state + - pipeline_details + - iteration + - started + - stopped + - submitted + - url + title: ComputationGet + ComputationStart: + properties: + force_restart: + type: boolean + title: Force Restart + default: false + subgraph: + items: + type: string + type: array + uniqueItems: true + title: Subgraph + default: [] + type: object + title: ComputationStart + ConnectServiceToPricingPlanBodyParams: + properties: + serviceKey: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Servicekey + serviceVersion: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Serviceversion + type: object + required: + - serviceKey + - serviceVersion + title: ConnectServiceToPricingPlanBodyParams + CountryInfoDict: + properties: + name: + type: string + title: Name + alpha2: + type: string + title: Alpha2 + type: object + required: + - name + - alpha2 + title: CountryInfoDict + CreatePricingPlanBodyParams: + properties: + displayName: + type: string + title: Displayname + description: + type: string + title: Description + classification: + $ref: '#/components/schemas/PricingPlanClassification' + pricingPlanKey: + type: string + title: Pricingplankey + type: object + required: + - displayName + - description + - classification + - pricingPlanKey + title: CreatePricingPlanBodyParams + CreatePricingUnitBodyParams: + properties: + unitName: + type: string + title: Unitname + unitExtraInfo: + $ref: '#/components/schemas/UnitExtraInfo-Input' + default: + type: boolean + title: Default + specificInfo: + $ref: '#/components/schemas/SpecificInfo' + costPerUnit: + anyOf: + - type: number + - type: string + title: Costperunit + comment: + type: string + title: Comment + type: object + required: + - unitName + - unitExtraInfo + - default + - specificInfo + - costPerUnit + - comment + title: CreatePricingUnitBodyParams + CreateWalletBodyParams: + properties: + name: + type: string + title: Name + description: + anyOf: + - type: string + - type: 'null' + title: Description + thumbnail: + anyOf: + - type: string + - type: 'null' + title: Thumbnail + type: object + required: + - name + title: CreateWalletBodyParams + CreateWalletPayment: + properties: + priceDollars: + anyOf: + - type: number + exclusiveMaximum: true + exclusiveMinimum: true + maximum: 1000000.0 + minimum: 0.0 + - type: string + title: Pricedollars + comment: + anyOf: + - type: string + maxLength: 100 + - type: 'null' + title: Comment + type: object + required: + - priceDollars + title: CreateWalletPayment + DatCoreFileLink: + properties: + store: + type: integer + title: Store + description: 'The store identifier: 0 for simcore S3, 1 for datcore' + path: + anyOf: + - type: string + pattern: ^(api|([0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{12}))\/([0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{12})\/(.+)$ + - type: string + pattern: ^N:package:[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{12}$ + title: Path + description: The path to the file in the storage provider domain + label: + type: string + title: Label + description: The real file name + eTag: + anyOf: + - type: string + - type: 'null' + title: Etag + description: Entity tag that uniquely represents the file. The method to + generate the tag is not specified (black box). + dataset: + type: string + title: Dataset + description: Unique identifier to access the dataset on datcore (REQUIRED + for datcore) + additionalProperties: false + type: object + required: + - store + - path + - label + - dataset + title: DatCoreFileLink + description: I/O port type to hold a link to a file in DATCORE storage + DataExportPost: + properties: + paths: + items: + type: string + format: path + type: array + title: Paths + type: object + required: + - paths + title: DataExportPost + DatasetMetaData: + properties: + dataset_id: + anyOf: + - type: string + - type: 'null' + title: Dataset Id + display_name: + anyOf: + - type: string + - type: 'null' + title: Display Name + type: object + title: DatasetMetaData + example: + dataset_id: N:id-aaaa + display_name: simcore-testing + DownloadLink: + properties: + downloadLink: + type: string + title: Downloadlink + label: + anyOf: + - type: string + - type: 'null' + title: Label + description: Display name + additionalProperties: false + type: object + required: + - downloadLink + title: DownloadLink + description: I/O port type to hold a generic download link to a file (e.g. S3 + pre-signed link, etc) + EmailTestFailed: + properties: + test_name: + type: string + title: Test Name + error_type: + type: string + title: Error Type + error_message: + type: string + title: Error Message + traceback: + type: string + title: Traceback + type: object + required: + - test_name + - error_type + - error_message + - traceback + title: EmailTestFailed + EmailTestPassed: + properties: + fixtures: + type: object + title: Fixtures + info: + type: object + title: Info + type: object + required: + - fixtures + - info + title: EmailTestPassed + EmptyModel: + properties: {} + additionalProperties: false + type: object + title: EmptyModel + Envelope_AnyUrl_: + properties: + data: + anyOf: + - type: string + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[AnyUrl] + Envelope_ApiKeyCreateResponse_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/ApiKeyCreateResponse' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[ApiKeyCreateResponse] + Envelope_ApiKeyGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/ApiKeyGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[ApiKeyGet] + Envelope_AppStatusCheck_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/AppStatusCheck' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[AppStatusCheck] + Envelope_AsyncJobGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/AsyncJobGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[AsyncJobGet] + Envelope_AsyncJobStatus_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/AsyncJobStatus' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[AsyncJobStatus] + Envelope_CatalogServiceGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/CatalogServiceGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[CatalogServiceGet] + Envelope_ComputationGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/ComputationGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[ComputationGet] + Envelope_FileMetaDataGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/FileMetaDataGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[FileMetaDataGet] + Envelope_FileUploadCompleteFutureResponse_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/FileUploadCompleteFutureResponse' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[FileUploadCompleteFutureResponse] + Envelope_FileUploadCompleteResponse_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/FileUploadCompleteResponse' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[FileUploadCompleteResponse] + Envelope_FileUploadCompletionBody_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/FileUploadCompletionBody' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[FileUploadCompletionBody] + Envelope_FileUploadSchema_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/FileUploadSchema' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[FileUploadSchema] + Envelope_FolderGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/FolderGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[FolderGet] + Envelope_GetCreditPrice_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/GetCreditPrice' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[GetCreditPrice] + Envelope_GetProjectInactivityResponse_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/GetProjectInactivityResponse' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[GetProjectInactivityResponse] + Envelope_GetWalletAutoRecharge_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/GetWalletAutoRecharge' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[GetWalletAutoRecharge] + Envelope_GroupGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/GroupGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[GroupGet] + Envelope_GroupUserGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/GroupUserGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[GroupUserGet] + Envelope_HealthInfoDict_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/HealthInfoDict' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[HealthInfoDict] + Envelope_InvitationGenerated_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/InvitationGenerated' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[InvitationGenerated] + Envelope_InvitationInfo_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/InvitationInfo' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[InvitationInfo] + Envelope_LicensedItemPurchaseGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/LicensedItemPurchaseGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[LicensedItemPurchaseGet] + Envelope_Log_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/Log' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[Log] + Envelope_LoginNextPage_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/LoginNextPage' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[LoginNextPage] + Envelope_MyGroupsGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/MyGroupsGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[MyGroupsGet] + Envelope_MyProfileGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/MyProfileGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[MyProfileGet] + Envelope_MyTokenGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/MyTokenGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[MyTokenGet] + Envelope_NodeCreated_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/NodeCreated' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[NodeCreated] + Envelope_NodeRetrieved_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/NodeRetrieved' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[NodeRetrieved] + Envelope_PaymentMethodGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/PaymentMethodGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[PaymentMethodGet] + Envelope_PaymentMethodInitiated_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/PaymentMethodInitiated' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[PaymentMethodInitiated] + Envelope_PresignedLink_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/PresignedLink' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[PresignedLink] + Envelope_PricingPlanAdminGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/PricingPlanAdminGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[PricingPlanAdminGet] + Envelope_PricingPlanGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/PricingPlanGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[PricingPlanGet] + Envelope_PricingPlanToServiceAdminGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/PricingPlanToServiceAdminGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[PricingPlanToServiceAdminGet] + Envelope_PricingUnitAdminGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/PricingUnitAdminGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[PricingUnitAdminGet] + Envelope_PricingUnitGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/PricingUnitGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[PricingUnitGet] + Envelope_ProductGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/ProductGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[ProductGet] + Envelope_ProductUIGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/ProductUIGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[ProductUIGet] + Envelope_ProjectGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/ProjectGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[ProjectGet] + Envelope_ProjectGroupGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/ProjectGroupGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[ProjectGroupGet] + Envelope_ProjectMetadataGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/ProjectMetadataGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[ProjectMetadataGet] + Envelope_ProjectState_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/ProjectState' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[ProjectState] + Envelope_ProjectsCommentsAPI_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/ProjectsCommentsAPI' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[ProjectsCommentsAPI] + Envelope_RegisterPhoneNextPage_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/RegisterPhoneNextPage' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[RegisterPhoneNextPage] + Envelope_ResearchResource_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/ResearchResource' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[ResearchResource] + Envelope_ServiceInputGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/ServiceInputGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[ServiceInputGet] + Envelope_ServicePricingPlanGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/ServicePricingPlanGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[ServicePricingPlanGet] + Envelope_StatusDiagnosticsGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/StatusDiagnosticsGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[StatusDiagnosticsGet] + Envelope_TagGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/TagGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[TagGet] + Envelope_TaskGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/TaskGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[TaskGet] + Envelope_TaskStatus_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/TaskStatus' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[TaskStatus] + Envelope_Union_EmailTestFailed__EmailTestPassed__: + properties: + data: + anyOf: + - $ref: '#/components/schemas/EmailTestFailed' + - $ref: '#/components/schemas/EmailTestPassed' + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[Union[EmailTestFailed, EmailTestPassed]] + Envelope_Union_NodeGetIdle__NodeGetUnknown__RunningDynamicServiceDetails__NodeGet__: + properties: + data: + anyOf: + - $ref: '#/components/schemas/NodeGetIdle' + - $ref: '#/components/schemas/NodeGetUnknown' + - $ref: '#/components/schemas/RunningDynamicServiceDetails' + - $ref: '#/components/schemas/NodeGet' + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[Union[NodeGetIdle, NodeGetUnknown, RunningDynamicServiceDetails, + NodeGet]] + Envelope_Union_PricingUnitGet__NoneType__: + properties: + data: + anyOf: + - $ref: '#/components/schemas/PricingUnitGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[Union[PricingUnitGet, NoneType]] + Envelope_Union_WalletGet__NoneType__: + properties: + data: + anyOf: + - $ref: '#/components/schemas/WalletGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[Union[WalletGet, NoneType]] + Envelope_UserForAdminGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/UserForAdminGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[UserForAdminGet] + Envelope_WalletGetWithAvailableCredits_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/WalletGetWithAvailableCredits' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[WalletGetWithAvailableCredits] + Envelope_WalletGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/WalletGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[WalletGet] + Envelope_WalletGroupGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/WalletGroupGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[WalletGroupGet] + Envelope_WalletPaymentInitiated_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/WalletPaymentInitiated' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[WalletPaymentInitiated] + Envelope_WorkspaceGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/WorkspaceGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[WorkspaceGet] + Envelope_WorkspaceGroupGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/WorkspaceGroupGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[WorkspaceGroupGet] + Envelope__ComputationStarted_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/_ComputationStarted' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[_ComputationStarted] + Envelope__ProjectGroupAccess_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/_ProjectGroupAccess' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[_ProjectGroupAccess] + Envelope__ProjectNodePreview_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/_ProjectNodePreview' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[_ProjectNodePreview] + Envelope_dict_Annotated_str__StringConstraints___ImageResources__: + properties: + data: + anyOf: + - type: object + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[dict[Annotated[str, StringConstraints], ImageResources]] + Envelope_dict_Literal__comment_id____Annotated_int__Gt___: + properties: + data: + anyOf: + - additionalProperties: + type: integer + exclusiveMinimum: true + minimum: 0 + propertyNames: + const: comment_id + type: object + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[dict[Literal['comment_id'], Annotated[int, Gt]]] + Envelope_dict_UUID__Activity__: + properties: + data: + anyOf: + - additionalProperties: + $ref: '#/components/schemas/Activity' + propertyNames: + format: uuid + type: object + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[dict[UUID, Activity]] + Envelope_dict_UUID__ProjectInputGet__: + properties: + data: + anyOf: + - additionalProperties: + $ref: '#/components/schemas/ProjectInputGet' + propertyNames: + format: uuid + type: object + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[dict[UUID, ProjectInputGet]] + Envelope_dict_UUID__ProjectOutputGet__: + properties: + data: + anyOf: + - additionalProperties: + $ref: '#/components/schemas/ProjectOutputGet' + propertyNames: + format: uuid + type: object + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[dict[UUID, ProjectOutputGet]] + Envelope_dict_str__Any__: + properties: + data: + anyOf: + - type: object + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[dict[str, Any]] + Envelope_list_Annotated_str__StringConstraints___: + properties: + data: + anyOf: + - items: + type: string + pattern: ^[-_a-zA-Z0-9]+$ + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[Annotated[str, StringConstraints]]] + Envelope_list_Announcement__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/Announcement' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[Announcement]] + Envelope_list_ApiKeyGet__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/ApiKeyGet' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[ApiKeyGet]] + Envelope_list_DatasetMetaData__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/DatasetMetaData' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[DatasetMetaData]] + Envelope_list_FileMetaDataGet__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/FileMetaDataGet' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[FileMetaDataGet]] + Envelope_list_FolderGet__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/FolderGet' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[FolderGet]] + Envelope_list_GroupUserGet__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/GroupUserGet' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[GroupUserGet]] + Envelope_list_MyPermissionGet__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/MyPermissionGet' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[MyPermissionGet]] + Envelope_list_MyTokenGet__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/MyTokenGet' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[MyTokenGet]] + Envelope_list_PaymentMethodGet__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/PaymentMethodGet' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[PaymentMethodGet]] + Envelope_list_PricingPlanToServiceAdminGet__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/PricingPlanToServiceAdminGet' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[PricingPlanToServiceAdminGet]] + Envelope_list_ProjectGroupGet__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/ProjectGroupGet' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[ProjectGroupGet]] + Envelope_list_ProjectMetadataPortGet__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/ProjectMetadataPortGet' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[ProjectMetadataPortGet]] + Envelope_list_ProjectsCommentsAPI__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/ProjectsCommentsAPI' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[ProjectsCommentsAPI]] + Envelope_list_ResourceHit__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/ResourceHit' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[ResourceHit]] + Envelope_list_ServiceGet__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/ServiceGet' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[ServiceGet]] + Envelope_list_ServiceInputGet__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/ServiceInputGet' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[ServiceInputGet]] + Envelope_list_ServiceOutputGet__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/ServiceOutputGet' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[ServiceOutputGet]] + Envelope_list_TagGet__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/TagGet' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[TagGet]] + Envelope_list_TagGroupGet__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/TagGroupGet' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[TagGroupGet]] + Envelope_list_TaskGet__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/TaskGet' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[TaskGet]] + Envelope_list_UserForAdminGet__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/UserForAdminGet' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[UserForAdminGet]] + Envelope_list_UserGet__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/UserGet' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[UserGet]] + Envelope_list_UserNotification__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/UserNotification' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[UserNotification]] + Envelope_list_Viewer__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/Viewer' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[Viewer]] + Envelope_list_WalletGetWithAvailableCredits__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/WalletGetWithAvailableCredits' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[WalletGetWithAvailableCredits]] + Envelope_list_WalletGroupGet__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/WalletGroupGet' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[WalletGroupGet]] + Envelope_list_WorkspaceGet__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/WorkspaceGet' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[WorkspaceGet]] + Envelope_list_WorkspaceGroupGet__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/WorkspaceGroupGet' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[WorkspaceGroupGet]] + Envelope_list__ProjectNodePreview__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/_ProjectNodePreview' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[_ProjectNodePreview]] + Envelope_str_: + properties: + data: + anyOf: + - type: string + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[str] + EnvelopedError: + properties: + data: + type: 'null' + title: Data + error: + $ref: '#/components/schemas/ErrorGet' + type: object + required: + - error + title: EnvelopedError + ErrorGet: + properties: + message: + type: string + minLength: 5 + title: Message + description: Message displayed to the user + supportId: + anyOf: + - type: string + maxLength: 100 + minLength: 1 + - type: 'null' + title: Supportid + description: ID to track the incident during support + status: + type: integer + title: Status + default: 400 + deprecated: true + errors: + items: + $ref: '#/components/schemas/ErrorItemType' + type: array + title: Errors + default: [] + deprecated: true + logs: + items: + $ref: '#/components/schemas/LogMessageType' + type: array + title: Logs + default: [] + deprecated: true + type: object + required: + - message + title: ErrorGet + ErrorItemType: + properties: + code: + type: string + title: Code + message: + type: string + title: Message + resource: + anyOf: + - type: string + - type: 'null' + title: Resource + field: + anyOf: + - type: string + - type: 'null' + title: Field + type: object + required: + - code + - message + - resource + - field + title: ErrorItemType + FeaturesDict: + properties: + name: + type: string + title: Name + version: + type: string + title: Version + sex: + type: string + title: Sex + age: + type: string + title: Age + weight: + type: string + title: Weight + height: + type: string + title: Height + date: + type: string + title: Date + ethnicity: + type: string + title: Ethnicity + functionality: + type: string + title: Functionality + type: object + required: + - date + title: FeaturesDict + FileLocation: + properties: + name: + type: string + title: Name + id: + type: integer + title: Id + additionalProperties: false + type: object + required: + - name + - id + title: FileLocation + FileMetaData: + properties: + file_uuid: + anyOf: + - type: string + - type: 'null' + title: File Uuid + location_id: + anyOf: + - type: string + - type: 'null' + title: Location Id + project_name: + anyOf: + - type: string + - type: 'null' + title: Project Name + node_name: + anyOf: + - type: string + - type: 'null' + title: Node Name + file_name: + anyOf: + - type: string + - type: 'null' + title: File Name + file_id: + anyOf: + - type: string + - type: 'null' + title: File Id + created_at: + anyOf: + - type: string + - type: 'null' + title: Created At + last_modified: + anyOf: + - type: string + - type: 'null' + title: Last Modified + file_size: + anyOf: + - type: integer + - type: 'null' + title: File Size + entity_tag: + anyOf: + - type: string + - type: 'null' + title: Entity Tag + is_directory: + anyOf: + - type: boolean + - type: 'null' + title: Is Directory + type: object + title: FileMetaData + example: + created_at: '2019-06-19T12:29:03.308611Z' + entity_tag: a87ff679a2f3e71d9181a67b7542122c + file_id: N:package:e263da07-2d89-45a6-8b0f-61061b913873 + file_name: example.txt + file_size: 73 + file_uuid: simcore-testing/105/1000/3 + is_directory: false + last_modified: '2019-06-19T12:29:03.78852Z' + location_id: '0' + node_name: alpha + project_name: futurology + FileMetaDataGet: + properties: + file_uuid: + type: string + title: File Uuid + description: NOT a unique ID, like (api|uuid)/uuid/file_name or DATCORE + folder structure + location_id: + type: integer + title: Location Id + description: Storage location + project_name: + anyOf: + - type: string + - type: 'null' + title: Project Name + description: optional project name, used by frontend to display path + node_name: + anyOf: + - type: string + - type: 'null' + title: Node Name + description: optional node name, used by frontend to display path + file_name: + type: string + title: File Name + description: Display name for a file + file_id: + anyOf: + - type: string + pattern: ^(api|([0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{12}))\/([0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{12})\/(.+)$ + - type: string + pattern: ^N:package:[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{12}$ + title: File Id + description: THIS IS the unique ID for the file. either (api|project_id)/node_id/file_name.ext + for S3 and N:package:UUID for datcore + created_at: + type: string + format: date-time + title: Created At + last_modified: + type: string + format: date-time + title: Last Modified + file_size: + anyOf: + - type: integer + const: -1 + - type: integer + minimum: 0 + title: File Size + description: File size in bytes (-1 means invalid) + default: -1 + entity_tag: + anyOf: + - type: string + - type: 'null' + title: Entity Tag + description: Entity tag (or ETag), represents a specific version of the + file, None if invalid upload or datcore + is_soft_link: + type: boolean + title: Is Soft Link + description: If true, this file is a soft link.i.e. is another entry with + the same object_name + default: false + is_directory: + type: boolean + title: Is Directory + description: if True this is a directory + default: false + sha256_checksum: + anyOf: + - type: string + pattern: ^[a-fA-F0-9]{64}$ + - type: 'null' + title: Sha256 Checksum + description: 'SHA256 message digest of the file content. Main purpose: cheap + lookup.' + type: object + required: + - file_uuid + - location_id + - file_name + - file_id + - created_at + - last_modified + title: FileMetaDataGet + FileUploadCompleteFutureResponse: + properties: + state: + $ref: '#/components/schemas/FileUploadCompleteState' + e_tag: + anyOf: + - type: string + - type: 'null' + title: E Tag + type: object + required: + - state + title: FileUploadCompleteFutureResponse + FileUploadCompleteLinks: + properties: + state: + type: string + title: State + type: object + required: + - state + title: FileUploadCompleteLinks + FileUploadCompleteResponse: + properties: + links: + $ref: '#/components/schemas/FileUploadCompleteLinks' + type: object + required: + - links + title: FileUploadCompleteResponse + FileUploadCompleteState: + type: string + enum: + - ok + - nok + title: FileUploadCompleteState + FileUploadCompletionBody: + properties: + parts: + items: + $ref: '#/components/schemas/UploadedPart' + type: array + title: Parts + type: object + required: + - parts + title: FileUploadCompletionBody + FileUploadLinks: + properties: + abort_upload: + type: string + title: Abort Upload + complete_upload: + type: string + title: Complete Upload + type: object + required: + - abort_upload + - complete_upload + title: FileUploadLinks + FileUploadSchema: + properties: + chunk_size: + type: integer + minimum: 0 + title: Chunk Size + urls: + items: + type: string + type: array + title: Urls + links: + $ref: '#/components/schemas/FileUploadLinks' + additionalProperties: false + type: object + required: + - chunk_size + - urls + - links + title: FileUploadSchema + FolderCreateBodyParams: + properties: + name: + type: string + maxLength: 100 + minLength: 1 + title: Name + parentFolderId: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Parentfolderid + workspaceId: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Workspaceid + additionalProperties: false + type: object + required: + - name + title: FolderCreateBodyParams + FolderGet: + properties: + folderId: + type: integer + exclusiveMinimum: true + title: Folderid + minimum: 0 + parentFolderId: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Parentfolderid + name: + type: string + title: Name + createdAt: + type: string + format: date-time + title: Createdat + modifiedAt: + type: string + format: date-time + title: Modifiedat + trashedAt: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Trashedat + trashedBy: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Trashedby + description: The primary gid of the user who trashed + owner: + type: integer + exclusiveMinimum: true + title: Owner + minimum: 0 + workspaceId: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Workspaceid + myAccessRights: + $ref: '#/components/schemas/AccessRights' + type: object + required: + - folderId + - name + - createdAt + - modifiedAt + - trashedAt + - trashedBy + - owner + - workspaceId + - myAccessRights + title: FolderGet + FolderReplaceBodyParams: + properties: + name: + type: string + maxLength: 100 + minLength: 1 + title: Name + parentFolderId: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Parentfolderid + additionalProperties: false + type: object + required: + - name + title: FolderReplaceBodyParams + GenerateInvitation: + properties: + guest: + type: string + format: email + title: Guest + trialAccountDays: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Trialaccountdays + extraCreditsInUsd: + anyOf: + - type: integer + exclusiveMaximum: true + minimum: 0 + maximum: 500 + - type: 'null' + title: Extracreditsinusd + type: object + required: + - guest + title: GenerateInvitation + GetCreditPrice: + properties: + productName: + type: string + title: Productname + usdPerCredit: + anyOf: + - type: number + minimum: 0.0 + - type: 'null' + title: Usdpercredit + description: Price of a credit in USD. If None, then this product's price + is UNDEFINED + minPaymentAmountUsd: + anyOf: + - type: integer + minimum: 0 + - type: 'null' + title: Minpaymentamountusd + description: Minimum amount (included) in USD that can be paid for this + productCan be None if this product's price is UNDEFINED + type: object + required: + - productName + - usdPerCredit + - minPaymentAmountUsd + title: GetCreditPrice + GetProductTemplate: + properties: + id: + type: string + maxLength: 100 + minLength: 1 + title: Id + content: + type: string + title: Content + type: object + required: + - id + - content + title: GetProductTemplate + GetProjectInactivityResponse: + properties: + is_inactive: + type: boolean + title: Is Inactive + type: object + required: + - is_inactive + title: GetProjectInactivityResponse + example: + is_inactive: 'false' + GetWalletAutoRecharge: + properties: + enabled: + type: boolean + title: Enabled + description: Enables/disables auto-recharge trigger in this wallet + default: false + paymentMethodId: + anyOf: + - type: string + maxLength: 100 + minLength: 1 + - type: 'null' + title: Paymentmethodid + description: Payment method in the wallet used to perform the auto-recharge + payments or None if still undefined + minBalanceInCredits: + type: string + title: Minbalanceincredits + description: Minimum balance in credits that triggers an auto-recharge [Read + only] + topUpAmountInUsd: + type: string + title: Topupamountinusd + description: Amount in USD payed when auto-recharge condition is satisfied + monthlyLimitInUsd: + anyOf: + - type: string + - type: 'null' + title: Monthlylimitinusd + description: Maximum amount in USD charged within a natural month.None indicates + no limit. + type: object + required: + - paymentMethodId + - minBalanceInCredits + - topUpAmountInUsd + - monthlyLimitInUsd + title: GetWalletAutoRecharge + GroupAccessRights: + properties: + read: + type: boolean + title: Read + write: + type: boolean + title: Write + delete: + type: boolean + title: Delete + type: object + required: + - read + - write + - delete + title: GroupAccessRights + description: defines acesss rights for the user + GroupCreate: + properties: + label: + type: string + title: Label + description: + type: string + title: Description + thumbnail: + anyOf: + - type: string + minLength: 1 + format: uri + - type: 'null' + title: Thumbnail + type: object + required: + - label + - description + title: GroupCreate + GroupGet: + properties: + gid: + type: integer + exclusiveMinimum: true + title: Gid + description: the group ID + minimum: 0 + label: + type: string + title: Label + description: the group name + description: + type: string + title: Description + description: the group description + thumbnail: + anyOf: + - type: string + - type: 'null' + title: Thumbnail + description: url to the group thumbnail + accessRights: + $ref: '#/components/schemas/GroupAccessRights' + inclusionRules: + additionalProperties: + type: string + type: object + title: Inclusionrules + deprecated: true + type: object + required: + - gid + - label + - description + - accessRights + title: GroupGet + GroupUpdate: + properties: + label: + anyOf: + - type: string + - type: 'null' + title: Label + description: + anyOf: + - type: string + - type: 'null' + title: Description + thumbnail: + anyOf: + - type: string + minLength: 1 + format: uri + - type: 'null' + title: Thumbnail + type: object + title: GroupUpdate + GroupUserAdd: + properties: + uid: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Uid + userName: + anyOf: + - type: string + maxLength: 100 + minLength: 1 + - type: 'null' + title: Username + email: + anyOf: + - type: string + format: email + - type: 'null' + title: Email + description: Accessible only if the user has opted to share their email + in privacy settings + type: object + title: GroupUserAdd + description: "Identify the user with either `email` or `uid` \u2014 only one." + GroupUserGet: + properties: + id: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Id + description: the user's id + userName: + type: string + maxLength: 100 + minLength: 1 + title: Username + gid: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Gid + description: the user primary gid + login: + anyOf: + - type: string + format: email + - type: 'null' + title: Login + description: the user's email, if privacy settings allows + first_name: + anyOf: + - type: string + - type: 'null' + title: First Name + description: If privacy settings allows + last_name: + anyOf: + - type: string + - type: 'null' + title: Last Name + description: If privacy settings allows + gravatar_id: + anyOf: + - type: string + - type: 'null' + title: Gravatar Id + description: the user gravatar id hash + deprecated: true + accessRights: + anyOf: + - $ref: '#/components/schemas/GroupAccessRights' + - type: 'null' + description: If group is standard, these are these are the access rights + of the user to it.None if primary group. + type: object + required: + - userName + title: GroupUserGet + example: + accessRights: + delete: false + read: true + write: false + first_name: Mr + gid: '3' + gravatar_id: a1af5c6ecc38e81f29695f01d6ceb540 + id: '1' + last_name: Smith + login: mr.smith@matrix.com + userName: mrmith + GroupUserUpdate: + properties: + accessRights: + $ref: '#/components/schemas/GroupAccessRights' + type: object + required: + - accessRights + title: GroupUserUpdate + example: + accessRights: + delete: false + read: true + write: false + HardwareInfo: + properties: + aws_ec2_instances: + items: + type: string + type: array + title: Aws Ec2 Instances + type: object + required: + - aws_ec2_instances + title: HardwareInfo + HealthInfoDict: + properties: + name: + type: string + title: Name + version: + type: string + title: Version + api_version: + type: string + title: Api Version + type: object + required: + - name + - version + - api_version + title: HealthInfoDict + ImageResources: + properties: + image: + type: string + pattern: ^(?:([a-z0-9-]+(?:\.[a-z0-9-]+)+(?::\d+)?|[a-z0-9-]+:\d+)/)?((?:[a-z0-9][a-z0-9_.-]*/)*[a-z0-9-_]+[a-z0-9])(?::([\w][\w.-]{0,127}))?(\@sha256:[a-fA-F0-9]{32,64})?$ + title: Image + description: Used by the frontend to provide a context for the users.Services + with a docker-compose spec will have multiple entries.Using the `image:version` + instead of the docker-compose spec is more helpful for the end user. + resources: + additionalProperties: + $ref: '#/components/schemas/ResourceValue' + type: object + title: Resources + boot_modes: + items: + $ref: '#/components/schemas/BootMode' + type: array + title: Boot Modes + description: describe how a service shall be booted, using CPU, MPI, openMP + or GPU + default: + - CPU + type: object + required: + - image + - resources + title: ImageResources + example: + image: simcore/service/dynamic/pretty-intense:1.0.0 + resources: + AIRAM: + limit: 1 + reservation: 1 + ANY_resource: + limit: some_value + reservation: some_value + CPU: + limit: 4 + reservation: 0.1 + RAM: + limit: 103079215104 + reservation: 536870912 + VRAM: + limit: 1 + reservation: 1 + InvitationCheck: + properties: + invitation: + type: string + title: Invitation + description: Invitation code + additionalProperties: false + type: object + required: + - invitation + title: InvitationCheck + InvitationGenerated: + properties: + productName: + type: string + title: Productname + issuer: + type: string + title: Issuer + guest: + type: string + format: email + title: Guest + trialAccountDays: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Trialaccountdays + extraCreditsInUsd: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Extracreditsinusd + created: + type: string + format: date-time + title: Created + invitationLink: + type: string + title: Invitationlink + type: object + required: + - productName + - issuer + - guest + - created + - invitationLink + title: InvitationGenerated + InvitationInfo: + properties: + email: + anyOf: + - type: string + format: email + - type: 'null' + title: Email + description: Email associated to invitation or None + additionalProperties: false + type: object + title: InvitationInfo + LicensedItemPurchaseGet: + properties: + licensedItemPurchaseId: + type: string + format: uuid + title: Licenseditempurchaseid + productName: + type: string + title: Productname + licensedItemId: + type: string + format: uuid + title: Licenseditemid + key: + type: string + title: Key + version: + type: string + pattern: ^\d+\.\d+\.\d+$ + title: Version + walletId: + type: integer + exclusiveMinimum: true + title: Walletid + minimum: 0 + pricingUnitCostId: + type: integer + exclusiveMinimum: true + title: Pricingunitcostid + minimum: 0 + pricingUnitCost: + type: string + title: Pricingunitcost + startAt: + type: string + format: date-time + title: Startat + expireAt: + type: string + format: date-time + title: Expireat + numOfSeats: + type: integer + title: Numofseats + purchasedByUser: + type: integer + exclusiveMinimum: true + title: Purchasedbyuser + minimum: 0 + userEmail: + type: string + format: email + title: Useremail + purchasedAt: + type: string + format: date-time + title: Purchasedat + modifiedAt: + type: string + format: date-time + title: Modifiedat + type: object + required: + - licensedItemPurchaseId + - productName + - licensedItemId + - key + - version + - walletId + - pricingUnitCostId + - pricingUnitCost + - startAt + - expireAt + - numOfSeats + - purchasedByUser + - userEmail + - purchasedAt + - modifiedAt + title: LicensedItemPurchaseGet + LicensedItemRestGet: + properties: + licensedItemId: + type: string + format: uuid + title: Licenseditemid + key: + type: string + title: Key + version: + type: string + pattern: ^\d+\.\d+\.\d+$ + title: Version + displayName: + type: string + title: Displayname + licensedResourceType: + $ref: '#/components/schemas/LicensedResourceType' + licensedResources: + items: + $ref: '#/components/schemas/_ItisVipResourceRestData' + type: array + title: Licensedresources + pricingPlanId: + type: integer + exclusiveMinimum: true + title: Pricingplanid + minimum: 0 + categoryId: + type: string + maxLength: 100 + minLength: 1 + title: Categoryid + categoryDisplay: + type: string + title: Categorydisplay + categoryIcon: + anyOf: + - type: string + - type: 'null' + title: Categoryicon + termsOfUseUrl: + anyOf: + - type: string + - type: 'null' + title: Termsofuseurl + createdAt: + type: string + format: date-time + title: Createdat + modifiedAt: + type: string + format: date-time + title: Modifiedat + type: object + required: + - licensedItemId + - key + - version + - displayName + - licensedResourceType + - licensedResources + - pricingPlanId + - categoryId + - categoryDisplay + - createdAt + - modifiedAt + title: LicensedItemRestGet + LicensedItemsBodyParams: + properties: + wallet_id: + type: integer + exclusiveMinimum: true + title: Wallet Id + minimum: 0 + pricing_plan_id: + type: integer + exclusiveMinimum: true + title: Pricing Plan Id + minimum: 0 + pricing_unit_id: + type: integer + exclusiveMinimum: true + title: Pricing Unit Id + minimum: 0 + num_of_seats: + type: integer + title: Num Of Seats + additionalProperties: false + type: object + required: + - wallet_id + - pricing_plan_id + - pricing_unit_id + - num_of_seats + title: LicensedItemsBodyParams + LicensedResourceType: + type: string + enum: + - VIP_MODEL + title: LicensedResourceType + Limits: + properties: + cpus: + type: number + exclusiveMinimum: true + title: Cpus + minimum: 0.0 + mem: + type: number + exclusiveMinimum: true + title: Mem + minimum: 0.0 + type: object + required: + - cpus + - mem + title: Limits + LinkType: + type: string + enum: + - PRESIGNED + - S3 + title: LinkType + Log: + properties: + level: + anyOf: + - $ref: '#/components/schemas/LogLevel' + - type: 'null' + description: log level + default: INFO + message: + type: string + title: Message + description: log message. If logger is USER, then it MUST be human readable + logger: + anyOf: + - type: string + - type: 'null' + title: Logger + description: name of the logger receiving this message + type: object + required: + - message + title: Log + example: + level: INFO + logger: user-logger + message: Hi there, Mr user + LogLevel: + type: string + enum: + - DEBUG + - INFO + - WARNING + - ERROR + title: LogLevel + LogMessageType: + properties: + message: + type: string + title: Message + level: + type: string + title: Level + default: INFO + logger: + type: string + title: Logger + default: user + type: object + required: + - message + title: LogMessageType + LoginBody: + properties: + email: + type: string + format: email + title: Email + password: + type: string + format: password + title: Password + writeOnly: true + additionalProperties: false + type: object + required: + - email + - password + title: LoginBody + LoginNextPage: + properties: + name: + type: string + title: Name + description: Code name to the front-end page. Ideally a PageStr + parameters: + anyOf: + - $ref: '#/components/schemas/CodePageParams' + - type: 'null' + type: object + required: + - name + title: LoginNextPage + LoginTwoFactorAuthBody: + properties: + email: + type: string + format: email + title: Email + code: + type: string + format: password + title: Code + writeOnly: true + additionalProperties: false + type: object + required: + - email + - code + title: LoginTwoFactorAuthBody + LogoutBody: + properties: + client_session_id: + anyOf: + - type: string + - type: 'null' + title: Client Session Id + additionalProperties: false + type: object + title: LogoutBody + MarkerUI: + properties: + color: + type: string + format: color + title: Color + additionalProperties: false + type: object + required: + - color + title: MarkerUI + MyGroupsGet: + properties: + me: + $ref: '#/components/schemas/GroupGet' + organizations: + anyOf: + - items: + $ref: '#/components/schemas/GroupGet' + type: array + - type: 'null' + title: Organizations + all: + $ref: '#/components/schemas/GroupGet' + product: + anyOf: + - $ref: '#/components/schemas/GroupGet' + - type: 'null' + type: object + required: + - me + - all + title: MyGroupsGet + example: + all: + accessRights: + delete: false + read: true + write: false + description: Open to all users + gid: 1 + label: All + me: + accessRights: + delete: true + read: true + write: true + description: A very special user + gid: '27' + label: A user + organizations: + - accessRights: + delete: false + read: true + write: false + description: The Foundation for Research on Information Technologies in + Society + gid: '15' + label: ITIS Foundation + - accessRights: + delete: false + read: true + write: false + description: Some foundation + gid: '16' + label: Blue Fundation + MyPermissionGet: + properties: + name: + type: string + title: Name + allowed: + type: boolean + title: Allowed + type: object + required: + - name + - allowed + title: MyPermissionGet + MyProfileGet: + properties: + id: + type: integer + exclusiveMinimum: true + title: Id + minimum: 0 + userName: + type: string + maxLength: 100 + minLength: 1 + title: Username + description: Unique username identifier + first_name: + anyOf: + - type: string + maxLength: 255 + - type: 'null' + title: First Name + last_name: + anyOf: + - type: string + maxLength: 255 + - type: 'null' + title: Last Name + login: + type: string + format: email + title: Login + role: + type: string + enum: + - ANONYMOUS + - GUEST + - USER + - TESTER + - PRODUCT_OWNER + - ADMIN + title: Role + groups: + anyOf: + - $ref: '#/components/schemas/MyGroupsGet' + - type: 'null' + gravatar_id: + anyOf: + - type: string + - type: 'null' + title: Gravatar Id + deprecated: true + expirationDate: + anyOf: + - type: string + format: date + - type: 'null' + title: Expirationdate + description: If user has a trial account, it sets the expiration date, otherwise + None + privacy: + $ref: '#/components/schemas/MyProfilePrivacyGet' + preferences: + additionalProperties: + $ref: '#/components/schemas/Preference' + type: object + title: Preferences + type: object + required: + - id + - userName + - login + - role + - privacy + - preferences + title: MyProfileGet + MyProfilePatch: + properties: + first_name: + anyOf: + - type: string + maxLength: 255 + - type: 'null' + title: First Name + last_name: + anyOf: + - type: string + maxLength: 255 + - type: 'null' + title: Last Name + userName: + anyOf: + - type: string + maxLength: 100 + minLength: 1 + - type: 'null' + title: Username + privacy: + anyOf: + - $ref: '#/components/schemas/MyProfilePrivacyPatch' + - type: 'null' + type: object + title: MyProfilePatch + example: + first_name: Pedro + last_name: Crespo + MyProfilePrivacyGet: + properties: + hideFullname: + type: boolean + title: Hidefullname + hideEmail: + type: boolean + title: Hideemail + type: object + required: + - hideFullname + - hideEmail + title: MyProfilePrivacyGet + MyProfilePrivacyPatch: + properties: + hideFullname: + anyOf: + - type: boolean + - type: 'null' + title: Hidefullname + hideEmail: + anyOf: + - type: boolean + - type: 'null' + title: Hideemail + type: object + title: MyProfilePrivacyPatch + MyTokenCreate: + properties: + service: + type: string + maxLength: 100 + minLength: 1 + title: Service + description: uniquely identifies the service where this token is used + token_key: + type: string + maxLength: 100 + minLength: 1 + title: Token Key + token_secret: + type: string + maxLength: 100 + minLength: 1 + title: Token Secret + type: object + required: + - service + - token_key + - token_secret + title: MyTokenCreate + MyTokenGet: + properties: + service: + type: string + maxLength: 100 + minLength: 1 + title: Service + token_key: + type: string + maxLength: 100 + minLength: 1 + title: Token Key + token_secret: + anyOf: + - type: string + maxLength: 100 + minLength: 1 + - type: 'null' + title: Token Secret + description: Will be removed + deprecated: true + type: object + required: + - service + - token_key + title: MyTokenGet + Node-Input: + properties: + key: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Key + description: distinctive name for the node based on the docker registry + path + version: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Version + description: semantic version number of the node + label: + type: string + title: Label + description: The short name of the node + progress: + anyOf: + - type: number + maximum: 100.0 + minimum: 0.0 + - type: 'null' + title: Progress + description: the node progress value (deprecated in DB, still used for API + only) + deprecated: true + thumbnail: + anyOf: + - type: string + - type: string + maxLength: 2083 + minLength: 1 + format: uri + - type: 'null' + title: Thumbnail + description: url of the latest screenshot of the node + runHash: + anyOf: + - type: string + - type: 'null' + title: Runhash + description: the hex digest of the resolved inputs +outputs hash at the + time when the last outputs were generated + inputs: + anyOf: + - type: object + - type: 'null' + title: Inputs + description: values of input properties + inputsRequired: + items: + type: string + pattern: ^[-_a-zA-Z0-9]+$ + type: array + title: Inputsrequired + description: Defines inputs that are required in order to run the service + inputsUnits: + anyOf: + - type: object + - type: 'null' + title: Inputsunits + description: Overrides default unit (if any) defined in the service for + each port + inputAccess: + anyOf: + - type: object + - type: 'null' + title: Inputaccess + description: map with key - access level pairs + inputNodes: + anyOf: + - items: + type: string + format: uuid + type: array + - type: 'null' + title: Inputnodes + description: node IDs of where the node is connected to + outputs: + anyOf: + - type: object + - type: 'null' + title: Outputs + description: values of output properties + outputNode: + anyOf: + - type: boolean + - type: 'null' + title: Outputnode + deprecated: true + outputNodes: + anyOf: + - items: + type: string + format: uuid + type: array + - type: 'null' + title: Outputnodes + description: Used in group-nodes. Node IDs of those connected to the output + parent: + anyOf: + - type: string + format: uuid + - type: 'null' + title: Parent + description: Parent's (group-nodes') node ID s. Used to group + position: + anyOf: + - $ref: '#/components/schemas/Position' + - type: 'null' + description: Use projects_ui.WorkbenchUI.position instead + deprecated: true + state: + anyOf: + - $ref: '#/components/schemas/NodeState' + - type: 'null' + description: The node's state object + bootOptions: + anyOf: + - type: object + - type: 'null' + title: Bootoptions + description: Some services provide alternative parameters to be injected + at boot time. The user selection should be stored here, and it will overwrite + the services's defaults. + additionalProperties: false + type: object + required: + - key + - version + - label + title: Node + Node-Output: + properties: + key: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Key + description: distinctive name for the node based on the docker registry + path + version: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Version + description: semantic version number of the node + label: + type: string + title: Label + description: The short name of the node + progress: + anyOf: + - type: number + maximum: 100.0 + minimum: 0.0 + - type: 'null' + title: Progress + description: the node progress value (deprecated in DB, still used for API + only) + deprecated: true + thumbnail: + anyOf: + - type: string + - type: 'null' + title: Thumbnail + description: url of the latest screenshot of the node + runHash: + anyOf: + - type: string + - type: 'null' + title: Runhash + description: the hex digest of the resolved inputs +outputs hash at the + time when the last outputs were generated + inputs: + anyOf: + - type: object + - type: 'null' + title: Inputs + description: values of input properties + inputsRequired: + items: + type: string + pattern: ^[-_a-zA-Z0-9]+$ + type: array + title: Inputsrequired + description: Defines inputs that are required in order to run the service + inputsUnits: + anyOf: + - type: object + - type: 'null' + title: Inputsunits + description: Overrides default unit (if any) defined in the service for + each port + inputAccess: + anyOf: + - type: object + - type: 'null' + title: Inputaccess + description: map with key - access level pairs + inputNodes: + anyOf: + - items: + type: string + format: uuid + type: array + - type: 'null' + title: Inputnodes + description: node IDs of where the node is connected to + outputs: + anyOf: + - type: object + - type: 'null' + title: Outputs + description: values of output properties + outputNode: + anyOf: + - type: boolean + - type: 'null' + title: Outputnode + deprecated: true + outputNodes: + anyOf: + - items: + type: string + format: uuid + type: array + - type: 'null' + title: Outputnodes + description: Used in group-nodes. Node IDs of those connected to the output + parent: + anyOf: + - type: string + format: uuid + - type: 'null' + title: Parent + description: Parent's (group-nodes') node ID s. Used to group + position: + anyOf: + - $ref: '#/components/schemas/Position' + - type: 'null' + description: Use projects_ui.WorkbenchUI.position instead + deprecated: true + state: + anyOf: + - $ref: '#/components/schemas/NodeState' + - type: 'null' + description: The node's state object + bootOptions: + anyOf: + - type: object + - type: 'null' + title: Bootoptions + description: Some services provide alternative parameters to be injected + at boot time. The user selection should be stored here, and it will overwrite + the services's defaults. + additionalProperties: false + type: object + required: + - key + - version + - label + title: Node + NodeCreate: + properties: + service_key: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Service Key + service_version: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Service Version + service_id: + anyOf: + - type: string + - type: 'null' + title: Service Id + type: object + required: + - service_key + - service_version + title: NodeCreate + NodeCreated: + properties: + nodeId: + type: string + format: uuid + title: Nodeid + type: object + required: + - nodeId + title: NodeCreated + NodeGet: + properties: + publishedPort: + anyOf: + - type: integer + exclusiveMaximum: true + exclusiveMinimum: true + maximum: 65535 + minimum: 0 + - type: 'null' + title: Publishedport + description: The ports where the service provides its interface + entryPoint: + anyOf: + - type: string + - type: 'null' + title: Entrypoint + description: The entry point where the service provides its interface if + specified + serviceUuid: + type: string + title: Serviceuuid + description: The UUID attached to this service + serviceKey: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Servicekey + description: distinctive name for the node based on the docker registry + path + serviceVersion: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Serviceversion + description: semantic version number + serviceHost: + type: string + title: Servicehost + description: service host name within the network + servicePort: + type: integer + exclusiveMaximum: true + exclusiveMinimum: true + title: Serviceport + description: port to access the service within the network + default: 8080 + maximum: 65535 + minimum: 0 + serviceBasepath: + anyOf: + - type: string + - type: 'null' + title: Servicebasepath + description: different base path where current service is mounted otherwise + defaults to root + default: '' + serviceState: + $ref: '#/components/schemas/ServiceState' + description: 'the service state * ''pending'' - The service is waiting for + resources to start * ''pulling'' - The service is being pulled from the + registry * ''starting'' - The service is starting * ''running'' - The + service is running * ''complete'' - The service completed * ''failed'' + - The service failed to start + + ' + serviceMessage: + anyOf: + - type: string + - type: 'null' + title: Servicemessage + description: the service message + userId: + type: string + title: Userid + description: the user that started the service + type: object + required: + - publishedPort + - serviceUuid + - serviceKey + - serviceVersion + - serviceHost + - serviceState + - userId + title: NodeGet + NodeGetIdle: + properties: + serviceState: + type: string + const: idle + title: Servicestate + serviceUuid: + type: string + format: uuid + title: Serviceuuid + type: object + required: + - serviceState + - serviceUuid + title: NodeGetIdle + example: + service_state: idle + service_uuid: 3fa85f64-5717-4562-b3fc-2c963f66afa6 + NodeGetUnknown: + properties: + serviceState: + type: string + const: unknown + title: Servicestate + serviceUuid: + type: string + format: uuid + title: Serviceuuid + type: object + required: + - serviceState + - serviceUuid + title: NodeGetUnknown + example: + service_state: unknown + service_uuid: 3fa85f64-5717-4562-b3fc-2c963f66afa6 + NodeOutputs: + properties: + outputs: + type: object + title: Outputs + type: object + required: + - outputs + title: NodeOutputs + NodePatch: + properties: + key: + anyOf: + - type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + - type: 'null' + title: Key + version: + anyOf: + - type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + - type: 'null' + title: Version + label: + anyOf: + - type: string + - type: 'null' + title: Label + inputs: + type: object + title: Inputs + default: {} + inputsRequired: + anyOf: + - items: + type: string + pattern: ^[-_a-zA-Z0-9]+$ + type: array + - type: 'null' + title: Inputsrequired + inputNodes: + anyOf: + - items: + type: string + format: uuid + type: array + - type: 'null' + title: Inputnodes + progress: + anyOf: + - type: number + maximum: 100.0 + minimum: 0.0 + - type: 'null' + title: Progress + bootOptions: + anyOf: + - type: object + - type: 'null' + title: Bootoptions + outputs: + anyOf: + - type: object + - type: 'null' + title: Outputs + type: object + title: NodePatch + NodeRetrieve: + properties: + port_keys: + items: + type: string + pattern: ^[-_a-zA-Z0-9]+$ + type: array + title: Port Keys + default: [] + type: object + title: NodeRetrieve + NodeRetrieved: + properties: + sizeBytes: + type: integer + minimum: 0 + title: Sizebytes + description: The amount of data transferred by the retrieve call + type: object + required: + - sizeBytes + title: NodeRetrieved + NodeScreenshot: + properties: + thumbnail_url: + type: string + title: Thumbnail Url + file_url: + type: string + title: File Url + mimetype: + anyOf: + - type: string + - type: 'null' + title: Mimetype + description: File's media type or None if unknown. SEE https://www.iana.org/assignments/media-types/media-types.xhtml + type: object + required: + - thumbnail_url + - file_url + title: NodeScreenshot + NodeState: + properties: + modified: + type: boolean + title: Modified + description: true if the node's outputs need to be re-computed + default: true + dependencies: + items: + type: string + format: uuid + type: array + uniqueItems: true + title: Dependencies + description: contains the node inputs dependencies if they need to be computed + first + currentStatus: + $ref: '#/components/schemas/RunningState' + description: the node's current state + default: NOT_STARTED + progress: + anyOf: + - type: number + maximum: 1.0 + minimum: 0.0 + - type: 'null' + title: Progress + description: current progress of the task if available (None if not started + or not a computational task) + default: 0 + additionalProperties: false + type: object + title: NodeState + NotificationCategory: + type: string + enum: + - NEW_ORGANIZATION + - STUDY_SHARED + - TEMPLATE_SHARED + - ANNOTATION_NOTE + - WALLET_SHARED + title: NotificationCategory + OsparcCreditsAggregatedByServiceGet: + properties: + osparc_credits: + type: string + title: Osparc Credits + service_key: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Service Key + running_time_in_hours: + type: string + title: Running Time In Hours + type: object + required: + - osparc_credits + - service_key + - running_time_in_hours + title: OsparcCreditsAggregatedByServiceGet + Owner: + properties: + user_id: + type: integer + exclusiveMinimum: true + title: User Id + description: Owner's user id + minimum: 0 + first_name: + anyOf: + - type: string + maxLength: 255 + - type: 'null' + title: First Name + description: Owner's first name + last_name: + anyOf: + - type: string + maxLength: 255 + - type: 'null' + title: Last Name + description: Owner's last name + additionalProperties: false + type: object + required: + - user_id + - first_name + - last_name + title: Owner + PageLinks: + properties: + self: + type: string + title: Self + first: + type: string + title: First + prev: + anyOf: + - type: string + - type: 'null' + title: Prev + next: + anyOf: + - type: string + - type: 'null' + title: Next + last: + type: string + title: Last + additionalProperties: false + type: object + required: + - self + - first + - prev + - next + - last + title: PageLinks + PageMetaInfoLimitOffset: + properties: + limit: + type: integer + exclusiveMinimum: true + title: Limit + default: 20 + minimum: 0 + total: + type: integer + minimum: 0 + title: Total + offset: + type: integer + minimum: 0 + title: Offset + default: 0 + count: + type: integer + minimum: 0 + title: Count + additionalProperties: false + type: object + required: + - total + - count + title: PageMetaInfoLimitOffset + Page_CatalogServiceGet_: + properties: + _meta: + $ref: '#/components/schemas/PageMetaInfoLimitOffset' + _links: + $ref: '#/components/schemas/PageLinks' + data: + items: + $ref: '#/components/schemas/CatalogServiceGet' + type: array + title: Data + additionalProperties: false + type: object + required: + - _meta + - _links + - data + title: Page[CatalogServiceGet] + Page_LicensedItemPurchaseGet_: + properties: + _meta: + $ref: '#/components/schemas/PageMetaInfoLimitOffset' + _links: + $ref: '#/components/schemas/PageLinks' + data: + items: + $ref: '#/components/schemas/LicensedItemPurchaseGet' + type: array + title: Data + additionalProperties: false + type: object + required: + - _meta + - _links + - data + title: Page[LicensedItemPurchaseGet] + Page_LicensedItemRestGet_: + properties: + _meta: + $ref: '#/components/schemas/PageMetaInfoLimitOffset' + _links: + $ref: '#/components/schemas/PageLinks' + data: + items: + $ref: '#/components/schemas/LicensedItemRestGet' + type: array + title: Data + additionalProperties: false + type: object + required: + - _meta + - _links + - data + title: Page[LicensedItemRestGet] + Page_OsparcCreditsAggregatedByServiceGet_: + properties: + _meta: + $ref: '#/components/schemas/PageMetaInfoLimitOffset' + _links: + $ref: '#/components/schemas/PageLinks' + data: + items: + $ref: '#/components/schemas/OsparcCreditsAggregatedByServiceGet' + type: array + title: Data + additionalProperties: false + type: object + required: + - _meta + - _links + - data + title: Page[OsparcCreditsAggregatedByServiceGet] + Page_PaymentTransaction_: + properties: + _meta: + $ref: '#/components/schemas/PageMetaInfoLimitOffset' + _links: + $ref: '#/components/schemas/PageLinks' + data: + items: + $ref: '#/components/schemas/PaymentTransaction' + type: array + title: Data + additionalProperties: false + type: object + required: + - _meta + - _links + - data + title: Page[PaymentTransaction] + Page_PricingPlanAdminGet_: + properties: + _meta: + $ref: '#/components/schemas/PageMetaInfoLimitOffset' + _links: + $ref: '#/components/schemas/PageLinks' + data: + items: + $ref: '#/components/schemas/PricingPlanAdminGet' + type: array + title: Data + additionalProperties: false + type: object + required: + - _meta + - _links + - data + title: Page[PricingPlanAdminGet] + Page_PricingPlanGet_: + properties: + _meta: + $ref: '#/components/schemas/PageMetaInfoLimitOffset' + _links: + $ref: '#/components/schemas/PageLinks' + data: + items: + $ref: '#/components/schemas/PricingPlanGet' + type: array + title: Data + additionalProperties: false + type: object + required: + - _meta + - _links + - data + title: Page[PricingPlanGet] + Page_ProjectListItem_: + properties: + _meta: + $ref: '#/components/schemas/PageMetaInfoLimitOffset' + _links: + $ref: '#/components/schemas/PageLinks' + data: + items: + $ref: '#/components/schemas/ProjectListItem' + type: array + title: Data + additionalProperties: false + type: object + required: + - _meta + - _links + - data + title: Page[ProjectListItem] + Page_ServiceRunGet_: + properties: + _meta: + $ref: '#/components/schemas/PageMetaInfoLimitOffset' + _links: + $ref: '#/components/schemas/PageLinks' + data: + items: + $ref: '#/components/schemas/ServiceRunGet' + type: array + title: Data + additionalProperties: false + type: object + required: + - _meta + - _links + - data + title: Page[ServiceRunGet] + PatchRequestBody: + properties: + value: + title: Value + type: object + required: + - value + title: PatchRequestBody + PaymentMethodGet: + properties: + idr: + type: string + maxLength: 100 + minLength: 1 + title: Idr + walletId: + type: integer + exclusiveMinimum: true + title: Walletid + minimum: 0 + cardHolderName: + anyOf: + - type: string + - type: 'null' + title: Cardholdername + cardNumberMasked: + anyOf: + - type: string + - type: 'null' + title: Cardnumbermasked + cardType: + anyOf: + - type: string + - type: 'null' + title: Cardtype + expirationMonth: + anyOf: + - type: integer + - type: 'null' + title: Expirationmonth + expirationYear: + anyOf: + - type: integer + - type: 'null' + title: Expirationyear + created: + type: string + format: date-time + title: Created + autoRecharge: + type: boolean + title: Autorecharge + description: If true, this payment-method is used for auto-recharge + default: false + type: object + required: + - idr + - walletId + - created + title: PaymentMethodGet + PaymentMethodInitiated: + properties: + walletId: + type: integer + exclusiveMinimum: true + title: Walletid + minimum: 0 + paymentMethodId: + type: string + maxLength: 100 + minLength: 1 + title: Paymentmethodid + paymentMethodFormUrl: + type: string + title: Paymentmethodformurl + description: Link to external site that holds the payment submission form + type: object + required: + - walletId + - paymentMethodId + - paymentMethodFormUrl + title: PaymentMethodInitiated + PaymentTransaction: + properties: + paymentId: + type: string + maxLength: 100 + minLength: 1 + title: Paymentid + priceDollars: + type: string + title: Pricedollars + walletId: + type: integer + exclusiveMinimum: true + title: Walletid + minimum: 0 + osparcCredits: + type: string + title: Osparccredits + comment: + anyOf: + - type: string + - type: 'null' + title: Comment + createdAt: + type: string + format: date-time + title: Createdat + completedAt: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Completedat + completedStatus: + type: string + enum: + - PENDING + - SUCCESS + - FAILED + - CANCELED + title: Completedstatus + stateMessage: + anyOf: + - type: string + - type: 'null' + title: Statemessage + invoiceUrl: + anyOf: + - type: string + - type: 'null' + title: Invoiceurl + type: object + required: + - paymentId + - priceDollars + - walletId + - osparcCredits + - createdAt + - completedAt + - completedStatus + title: PaymentTransaction + PhoneConfirmationBody: + properties: + email: + type: string + format: email + title: Email + phone: + type: string + title: Phone + description: Phone number E.164, needed on the deployments with 2FA + code: + type: string + format: password + title: Code + writeOnly: true + additionalProperties: false + type: object + required: + - email + - phone + - code + title: PhoneConfirmationBody + PipelineDetails: + properties: + adjacency_list: + additionalProperties: + items: + type: string + format: uuid + type: array + propertyNames: + format: uuid + type: object + title: Adjacency List + description: 'The adjacency list of the current pipeline in terms of {NodeID: + [successor NodeID]}' + progress: + anyOf: + - type: number + maximum: 1.0 + minimum: 0.0 + - type: 'null' + title: Progress + description: the progress of the pipeline (None if there are no computational + tasks) + node_states: + additionalProperties: + $ref: '#/components/schemas/NodeState' + propertyNames: + format: uuid + type: object + title: Node States + description: The states of each of the computational nodes in the pipeline + type: object + required: + - adjacency_list + - progress + - node_states + title: PipelineDetails + PortLink: + properties: + nodeUuid: + type: string + format: uuid + title: Nodeuuid + description: The node to get the port output from + output: + type: string + pattern: ^[-_a-zA-Z0-9]+$ + title: Output + description: The port key in the node given by nodeUuid + additionalProperties: false + type: object + required: + - nodeUuid + - output + title: PortLink + description: I/O port type to reference to an output port of another node in + the same project + Position: + properties: + x: + type: integer + title: X + description: The x position + y: + type: integer + title: Y + description: The y position + additionalProperties: false + type: object + required: + - x + - y + title: Position + PreRegisteredUserGet: + properties: + firstName: + type: string + title: Firstname + lastName: + type: string + title: Lastname + email: + type: string + format: email + title: Email + institution: + anyOf: + - type: string + - type: 'null' + title: Institution + description: company, university, ... + phone: + anyOf: + - type: string + - type: 'null' + title: Phone + address: + type: string + title: Address + city: + type: string + title: City + state: + anyOf: + - type: string + - type: 'null' + title: State + postalCode: + type: string + title: Postalcode + country: + type: string + title: Country + extras: + type: object + title: Extras + description: Keeps extra information provided in the request form. + type: object + required: + - firstName + - lastName + - email + - phone + - address + - city + - postalCode + - country + title: PreRegisteredUserGet + Preference: + properties: + defaultValue: + title: Defaultvalue + description: used by the frontend + value: + title: Value + description: preference value + type: object + required: + - defaultValue + - value + title: Preference + PresignedLink: + properties: + link: + type: string + title: Link + type: object + required: + - link + title: PresignedLink + PricingPlanAdminGet: + properties: + pricingPlanId: + type: integer + exclusiveMinimum: true + title: Pricingplanid + minimum: 0 + displayName: + type: string + title: Displayname + description: + type: string + title: Description + classification: + $ref: '#/components/schemas/PricingPlanClassification' + createdAt: + type: string + format: date-time + title: Createdat + pricingPlanKey: + type: string + title: Pricingplankey + pricingUnits: + anyOf: + - items: + $ref: '#/components/schemas/PricingUnitAdminGet' + type: array + - type: 'null' + title: Pricingunits + isActive: + type: boolean + title: Isactive + type: object + required: + - pricingPlanId + - displayName + - description + - classification + - createdAt + - pricingPlanKey + - pricingUnits + - isActive + title: PricingPlanAdminGet + PricingPlanClassification: + type: string + enum: + - TIER + - LICENSE + title: PricingPlanClassification + PricingPlanGet: + properties: + pricingPlanId: + type: integer + exclusiveMinimum: true + title: Pricingplanid + minimum: 0 + displayName: + type: string + title: Displayname + description: + type: string + title: Description + classification: + $ref: '#/components/schemas/PricingPlanClassification' + createdAt: + type: string + format: date-time + title: Createdat + pricingPlanKey: + type: string + title: Pricingplankey + pricingUnits: + anyOf: + - items: + $ref: '#/components/schemas/PricingUnitGet' + type: array + - type: 'null' + title: Pricingunits + isActive: + type: boolean + title: Isactive + type: object + required: + - pricingPlanId + - displayName + - description + - classification + - createdAt + - pricingPlanKey + - pricingUnits + - isActive + title: PricingPlanGet + PricingPlanToServiceAdminGet: + properties: + pricingPlanId: + type: integer + exclusiveMinimum: true + title: Pricingplanid + minimum: 0 + serviceKey: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Servicekey + serviceVersion: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Serviceversion + created: + type: string + format: date-time + title: Created + type: object + required: + - pricingPlanId + - serviceKey + - serviceVersion + - created + title: PricingPlanToServiceAdminGet + PricingUnitAdminGet: + properties: + pricingUnitId: + type: integer + exclusiveMinimum: true + title: Pricingunitid + minimum: 0 + unitName: + type: string + title: Unitname + unitExtraInfo: + $ref: '#/components/schemas/UnitExtraInfo-Output' + currentCostPerUnit: + type: string + title: Currentcostperunit + default: + type: boolean + title: Default + specificInfo: + $ref: '#/components/schemas/HardwareInfo' + type: object + required: + - pricingUnitId + - unitName + - unitExtraInfo + - currentCostPerUnit + - default + - specificInfo + title: PricingUnitAdminGet + PricingUnitCostUpdate: + properties: + cost_per_unit: + anyOf: + - type: number + - type: string + title: Cost Per Unit + comment: + type: string + title: Comment + type: object + required: + - cost_per_unit + - comment + title: PricingUnitCostUpdate + PricingUnitGet: + properties: + pricingUnitId: + type: integer + exclusiveMinimum: true + title: Pricingunitid + minimum: 0 + unitName: + type: string + title: Unitname + unitExtraInfo: + $ref: '#/components/schemas/UnitExtraInfo-Output' + currentCostPerUnit: + type: string + title: Currentcostperunit + default: + type: boolean + title: Default + type: object + required: + - pricingUnitId + - unitName + - unitExtraInfo + - currentCostPerUnit + - default + title: PricingUnitGet + ProductGet: + properties: + name: + type: string + title: Name + displayName: + type: string + title: Displayname + shortName: + anyOf: + - type: string + - type: 'null' + title: Shortname + description: Short display name for SMS + vendor: + anyOf: + - type: object + - type: 'null' + title: Vendor + description: vendor attributes + issues: + anyOf: + - items: + type: object + type: array + - type: 'null' + title: Issues + description: Reference to issues tracker + manuals: + anyOf: + - items: + type: object + type: array + - type: 'null' + title: Manuals + description: List of manuals + support: + anyOf: + - items: + type: object + type: array + - type: 'null' + title: Support + description: List of support resources + loginSettings: + type: object + title: Loginsettings + maxOpenStudiesPerUser: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Maxopenstudiesperuser + isPaymentEnabled: + type: boolean + title: Ispaymentenabled + creditsPerUsd: + anyOf: + - type: string + - type: 'null' + title: Creditsperusd + templates: + items: + $ref: '#/components/schemas/GetProductTemplate' + type: array + title: Templates + description: List of templates available to this product for communications + (e.g. emails, sms, etc) + type: object + required: + - name + - displayName + - loginSettings + - maxOpenStudiesPerUser + - isPaymentEnabled + - creditsPerUsd + title: ProductGet + ProductUIGet: + properties: + productName: + type: string + title: Productname + ui: + type: object + title: Ui + description: Front-end owned ui product configuration + type: object + required: + - productName + - ui + title: ProductUIGet + ProjectCopyOverride: + properties: + name: + type: string + title: Name + description: + anyOf: + - type: string + - type: 'null' + title: Description + thumbnail: + anyOf: + - type: string + maxLength: 2083 + minLength: 1 + format: uri + - type: 'null' + title: Thumbnail + prjOwner: + type: string + format: email + title: Prjowner + type: object + required: + - name + - prjOwner + title: ProjectCopyOverride + ProjectCreateNew: + properties: + uuid: + anyOf: + - type: string + format: uuid + - type: 'null' + title: Uuid + name: + type: string + title: Name + description: + anyOf: + - type: string + - type: 'null' + title: Description + thumbnail: + anyOf: + - type: string + maxLength: 2083 + minLength: 1 + format: uri + - type: 'null' + title: Thumbnail + workbench: + type: object + title: Workbench + accessRights: + additionalProperties: + $ref: '#/components/schemas/AccessRights' + propertyNames: + maxLength: 100 + minLength: 1 + type: object + title: Accessrights + tags: + items: + type: integer + type: array + title: Tags + classifiers: + items: + type: string + type: array + title: Classifiers + ui: + anyOf: + - $ref: '#/components/schemas/StudyUI-Input' + - type: 'null' + workspaceId: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Workspaceid + folderId: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Folderid + type: object + required: + - name + - workbench + - accessRights + title: ProjectCreateNew + ProjectGet: + properties: + uuid: + type: string + format: uuid + title: Uuid + name: + type: string + title: Name + description: + type: string + title: Description + thumbnail: + anyOf: + - type: string + - type: string + const: '' + title: Thumbnail + workbench: + type: object + title: Workbench + prjOwner: + type: string + format: email + title: Prjowner + accessRights: + additionalProperties: + $ref: '#/components/schemas/AccessRights' + propertyNames: + maxLength: 100 + minLength: 1 + type: object + title: Accessrights + creationDate: + type: string + pattern: \d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z + title: Creationdate + lastChangeDate: + type: string + pattern: \d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z + title: Lastchangedate + state: + anyOf: + - $ref: '#/components/schemas/ProjectState' + - type: 'null' + trashedAt: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Trashedat + trashedBy: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Trashedby + description: The primary gid of the user who trashed + tags: + items: + type: integer + type: array + title: Tags + classifiers: + items: + type: string + type: array + title: Classifiers + default: [] + quality: + type: object + title: Quality + default: {} + ui: + anyOf: + - $ref: '#/components/schemas/EmptyModel' + - $ref: '#/components/schemas/StudyUI-Output' + - type: 'null' + title: Ui + dev: + anyOf: + - type: object + - type: 'null' + title: Dev + permalink: + anyOf: + - $ref: '#/components/schemas/ProjectPermalink' + - type: 'null' + workspaceId: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Workspaceid + folderId: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Folderid + type: object + required: + - uuid + - name + - description + - thumbnail + - workbench + - prjOwner + - accessRights + - creationDate + - lastChangeDate + - trashedAt + - trashedBy + - tags + - dev + - workspaceId + - folderId + title: ProjectGet + ProjectGroupGet: + properties: + gid: + type: integer + exclusiveMinimum: true + title: Gid + minimum: 0 + read: + type: boolean + title: Read + write: + type: boolean + title: Write + delete: + type: boolean + title: Delete + created: + type: string + format: date-time + title: Created + modified: + type: string + format: date-time + title: Modified + type: object + required: + - gid + - read + - write + - delete + - created + - modified + title: ProjectGroupGet + ProjectInputGet: + properties: + key: + type: string + format: uuid + title: Key + description: Project port's unique identifer. Same as the UUID of the associated + port node + value: + title: Value + description: Value assigned to this i/o port + label: + type: string + title: Label + type: object + required: + - key + - value + - label + title: ProjectInputGet + ProjectInputUpdate: + properties: + key: + type: string + format: uuid + title: Key + description: Project port's unique identifer. Same as the UUID of the associated + port node + value: + title: Value + description: Value assigned to this i/o port + type: object + required: + - key + - value + title: ProjectInputUpdate + ProjectListItem: + properties: + uuid: + type: string + format: uuid + title: Uuid + name: + type: string + title: Name + description: + type: string + title: Description + thumbnail: + anyOf: + - type: string + - type: string + const: '' + title: Thumbnail + workbench: + type: object + title: Workbench + prjOwner: + type: string + format: email + title: Prjowner + accessRights: + additionalProperties: + $ref: '#/components/schemas/AccessRights' + propertyNames: + maxLength: 100 + minLength: 1 + type: object + title: Accessrights + creationDate: + type: string + pattern: \d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z + title: Creationdate + lastChangeDate: + type: string + pattern: \d{4}-(12|11|10|0?[1-9])-(31|30|[0-2]?\d)T(2[0-3]|1\d|0?[0-9])(:(\d|[0-5]\d)){2}(\.\d{3})?Z + title: Lastchangedate + state: + anyOf: + - $ref: '#/components/schemas/ProjectState' + - type: 'null' + trashedAt: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Trashedat + trashedBy: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Trashedby + description: The primary gid of the user who trashed + tags: + items: + type: integer + type: array + title: Tags + classifiers: + items: + type: string + type: array + title: Classifiers + default: [] + quality: + type: object + title: Quality + default: {} + ui: + anyOf: + - $ref: '#/components/schemas/EmptyModel' + - $ref: '#/components/schemas/StudyUI-Output' + - type: 'null' + title: Ui + dev: + anyOf: + - type: object + - type: 'null' + title: Dev + permalink: + anyOf: + - $ref: '#/components/schemas/ProjectPermalink' + - type: 'null' + workspaceId: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Workspaceid + folderId: + anyOf: + - type: integer + exclusiveMinimum: true + minimum: 0 + - type: 'null' + title: Folderid + type: object + required: + - uuid + - name + - description + - thumbnail + - workbench + - prjOwner + - accessRights + - creationDate + - lastChangeDate + - trashedAt + - trashedBy + - tags + - dev + - workspaceId + - folderId + title: ProjectListItem + ProjectLocked: + properties: + value: + type: boolean + title: Value + description: True if the project is locked + status: + $ref: '#/components/schemas/ProjectStatus' + description: The status of the project + owner: + anyOf: + - $ref: '#/components/schemas/Owner' + - type: 'null' + description: If locked, the user that owns the lock + additionalProperties: false + type: object + required: + - value + - status + title: ProjectLocked + ProjectMetadataGet: + properties: + projectUuid: + type: string + format: uuid + title: Projectuuid + custom: + additionalProperties: + anyOf: + - type: boolean + - type: integer + - type: number + - type: string + type: object + title: Custom + description: Custom key-value map + type: object + required: + - projectUuid + title: ProjectMetadataGet + ProjectMetadataPortGet: + properties: + key: + type: string + format: uuid + title: Key + description: Project port's unique identifer. Same as the UUID of the associated + port node + kind: + type: string + enum: + - input + - output + title: Kind + content_schema: + anyOf: + - type: object + - type: 'null' + title: Content Schema + description: jsonschema for the port's value. SEE https://json-schema.org/understanding-json-schema/ + type: object + required: + - key + - kind + title: ProjectMetadataPortGet + ProjectMetadataUpdate: + properties: + custom: + additionalProperties: + anyOf: + - type: boolean + - type: integer + - type: number + - type: string + type: object + title: Custom + type: object + required: + - custom + title: ProjectMetadataUpdate + ProjectOutputGet: + properties: + key: + type: string + format: uuid + title: Key + description: Project port's unique identifer. Same as the UUID of the associated + port node + value: + title: Value + description: Value assigned to this i/o port + label: + type: string + title: Label + type: object + required: + - key + - value + - label + title: ProjectOutputGet + ProjectPatch: + properties: + name: + anyOf: + - type: string + - type: 'null' + title: Name + description: + anyOf: + - type: string + - type: 'null' + title: Description + thumbnail: + anyOf: + - type: string + maxLength: 2083 + minLength: 1 + format: uri + - type: 'null' + title: Thumbnail + accessRights: + anyOf: + - additionalProperties: + $ref: '#/components/schemas/AccessRights' + propertyNames: + maxLength: 100 + minLength: 1 + type: object + - type: 'null' + title: Accessrights + classifiers: + anyOf: + - items: + type: string + type: array + - type: 'null' + title: Classifiers + dev: + anyOf: + - type: object + - type: 'null' + title: Dev + ui: + anyOf: + - $ref: '#/components/schemas/StudyUI-Input' + - type: 'null' + quality: + anyOf: + - type: object + - type: 'null' + title: Quality + type: object + title: ProjectPatch + ProjectPermalink: + properties: + url: + type: string + title: Url + is_public: + type: boolean + title: Is Public + type: object + required: + - url + - is_public + title: ProjectPermalink + ProjectRunningState: + properties: + value: + $ref: '#/components/schemas/RunningState' + description: The running state of the project + additionalProperties: false + type: object + required: + - value + title: ProjectRunningState + ProjectState: + properties: + locked: + $ref: '#/components/schemas/ProjectLocked' + description: The project lock state + state: + $ref: '#/components/schemas/ProjectRunningState' + description: The project running state + additionalProperties: false + type: object + required: + - locked + - state + title: ProjectState + ProjectStatus: + type: string + enum: + - CLOSED + - CLOSING + - CLONING + - EXPORTING + - OPENING + - OPENED + - MAINTAINING + title: ProjectStatus + ProjectTypeAPI: + type: string + enum: + - all + - template + - user + title: ProjectTypeAPI + ProjectsCommentsAPI: + properties: + comment_id: + type: integer + exclusiveMinimum: true + title: Comment Id + description: Primary key, identifies the comment + minimum: 0 + project_uuid: + type: string + format: uuid + title: Project Uuid + description: project reference for this table + user_id: + type: integer + exclusiveMinimum: true + title: User Id + description: user reference for this table + minimum: 0 + contents: + type: string + title: Contents + description: Contents of the comment + created: + type: string + format: date-time + title: Created + description: Timestamp on creation + modified: + type: string + format: date-time + title: Modified + description: Timestamp with last update + additionalProperties: false + type: object + required: + - comment_id + - project_uuid + - user_id + - contents + - created + - modified + title: ProjectsCommentsAPI + PutWalletBodyParams: + properties: + name: + type: string + title: Name + description: + anyOf: + - type: string + - type: 'null' + title: Description + thumbnail: + anyOf: + - type: string + - type: 'null' + title: Thumbnail + status: + $ref: '#/components/schemas/WalletStatus' + type: object + required: + - name + - description + - thumbnail + - status + title: PutWalletBodyParams + RegisterBody: + properties: + email: + type: string + format: email + title: Email + password: + type: string + format: password + title: Password + writeOnly: true + confirm: + anyOf: + - type: string + format: password + writeOnly: true + - type: 'null' + title: Confirm + description: Password confirmation + invitation: + anyOf: + - type: string + - type: 'null' + title: Invitation + description: Invitation code + additionalProperties: false + type: object + required: + - email + - password + title: RegisterBody + RegisterPhoneBody: + properties: + email: + type: string + format: email + title: Email + phone: + type: string + title: Phone + description: Phone number E.164, needed on the deployments with 2FA + additionalProperties: false + type: object + required: + - email + - phone + title: RegisterPhoneBody + RegisterPhoneNextPage: + properties: + name: + type: string + title: Name + description: Code name to the front-end page. Ideally a PageStr + parameters: + anyOf: + - $ref: '#/components/schemas/_PageParams' + - type: 'null' + logger: + type: string + title: Logger + default: user + deprecated: true + level: + type: string + enum: + - INFO + - WARNING + - ERROR + title: Level + default: INFO + message: + type: string + title: Message + type: object + required: + - name + - message + title: RegisterPhoneNextPage + ReplaceWalletAutoRecharge: + properties: + enabled: + type: boolean + title: Enabled + paymentMethodId: + type: string + maxLength: 100 + minLength: 1 + title: Paymentmethodid + topUpAmountInUsd: + anyOf: + - type: number + minimum: 0.0 + - type: string + title: Topupamountinusd + monthlyLimitInUsd: + anyOf: + - type: number + minimum: 0.0 + - type: string + - type: 'null' + title: Monthlylimitinusd + type: object + required: + - enabled + - paymentMethodId + - topUpAmountInUsd + - monthlyLimitInUsd + title: ReplaceWalletAutoRecharge + ResearchResource: + properties: + rrid: + type: string + pattern: ^(RRID:)([^_\s]{1,30})_(\S{1,30})$ + title: Rrid + description: Unique identifier used as classifier, i.e. to tag studies and + services + name: + type: string + title: Name + description: + type: string + title: Description + type: object + required: + - rrid + - name + - description + title: ResearchResource + Resend2faBody: + properties: + email: + type: string + format: email + title: Email + description: User email (identifier) + via: + type: string + enum: + - SMS + - Email + title: Via + default: SMS + additionalProperties: false + type: object + required: + - email + title: Resend2faBody + ResetPasswordBody: + properties: + email: + type: string + title: Email + additionalProperties: false + type: object + required: + - email + title: ResetPasswordBody + ResetPasswordConfirmation: + properties: + password: + type: string + format: password + title: Password + writeOnly: true + confirm: + type: string + format: password + title: Confirm + writeOnly: true + additionalProperties: false + type: object + required: + - password + - confirm + title: ResetPasswordConfirmation + ResourceHit: + properties: + rid: + type: string + title: Rid + name: + type: string + title: Name + type: object + required: + - rid + - name + title: ResourceHit + ResourceValue: + properties: + limit: + anyOf: + - type: integer + - type: number + - type: string + title: Limit + reservation: + anyOf: + - type: integer + - type: number + - type: string + title: Reservation + type: object + required: + - limit + - reservation + title: ResourceValue + RunningDynamicServiceDetails: + properties: + service_key: + type: string + pattern: ^simcore/services/dynamic/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Service Key + description: distinctive name for the node based on the docker registry + path + service_version: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Service Version + description: semantic version number of the node + user_id: + type: integer + exclusiveMinimum: true + title: User Id + minimum: 0 + project_id: + type: string + format: uuid + title: Project Id + service_uuid: + type: string + format: uuid + title: Service Uuid + service_basepath: + anyOf: + - type: string + format: path + - type: 'null' + title: Service Basepath + description: predefined path where the dynamic service should be served. + If empty, the service shall use the root endpoint. + boot_type: + $ref: '#/components/schemas/ServiceBootType' + description: Describes how the dynamic services was started (legacy=V0, + modern=V2).Since legacy services do not have this label it defaults to + V0. + default: V0 + service_host: + type: string + title: Service Host + description: the service swarm internal host name + service_port: + type: integer + exclusiveMaximum: true + exclusiveMinimum: true + title: Service Port + description: the service swarm internal port + default: 8080 + maximum: 65535 + minimum: 0 + published_port: + anyOf: + - type: integer + exclusiveMaximum: true + exclusiveMinimum: true + maximum: 65535 + minimum: 0 + - type: 'null' + title: Published Port + description: the service swarm published port if any + deprecated: true + entry_point: + anyOf: + - type: string + - type: 'null' + title: Entry Point + description: if empty the service entrypoint is on the root endpoint. + deprecated: true + service_state: + $ref: '#/components/schemas/ServiceState' + description: service current state + service_message: + anyOf: + - type: string + - type: 'null' + title: Service Message + description: additional information related to service state + type: object + required: + - service_key + - service_version + - user_id + - project_id + - service_uuid + - service_host + - service_state + title: RunningDynamicServiceDetails + RunningState: + type: string + enum: + - UNKNOWN + - PUBLISHED + - NOT_STARTED + - PENDING + - WAITING_FOR_RESOURCES + - STARTED + - SUCCESS + - FAILED + - ABORTED + - WAITING_FOR_CLUSTER + title: RunningState + description: 'State of execution of a project''s computational workflow + + + SEE StateType for task state' + SelectBox: + properties: + structure: + items: + $ref: '#/components/schemas/Structure' + type: array + minItems: 1 + title: Structure + additionalProperties: false + type: object + required: + - structure + title: SelectBox + ServiceBootType: + type: string + enum: + - V0 + - V2 + title: ServiceBootType + ServiceGet: + properties: + key: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Key + description: Service key ID + title: + type: string + title: Title + description: Service name for display + description: + type: string + title: Description + description: Long description of the service + thumbnail: + type: string + title: Thumbnail + description: Url to service thumbnail + file_extensions: + items: + type: string + type: array + title: File Extensions + description: File extensions that this service can process + view_url: + type: string + title: View Url + description: Redirection to open a service in osparc (see /view) + type: object + required: + - key + - title + - description + - thumbnail + - view_url + title: ServiceGet + example: + description: It is also sim4life for the web + file_extensions: + - smash + - h5 + key: simcore/services/dynamic/sim4life + thumbnail: https://via.placeholder.com/170x120.png + title: Sim4Life Mattermost + view_url: https://host.com/view?viewer_key=simcore/services/dynamic/raw-graphs&viewer_version=1.2.3 + ServiceGroupAccessRightsV2: + properties: + execute: + type: boolean + title: Execute + default: false + write: + type: boolean + title: Write + default: false + additionalProperties: false + type: object + title: ServiceGroupAccessRightsV2 + ServiceInputGet: + properties: + unitLong: + anyOf: + - type: string + - type: 'null' + title: Unitlong + description: Long name of the unit for display (html-compatible), if available + unitShort: + anyOf: + - type: string + - type: 'null' + title: Unitshort + description: Short name for the unit for display (html-compatible), if available + displayOrder: + anyOf: + - type: number + - type: 'null' + title: Displayorder + description: 'DEPRECATED: new display order is taken from the item position. + This will be removed.' + deprecated: true + label: + type: string + title: Label + description: short name for the property + description: + type: string + title: Description + description: description of the property + type: + type: string + pattern: ^(number|integer|boolean|string|ref_contentSchema|data:([^/\s,]+/[^/\s,]+|\[[^/\s,]+/[^/\s,]+(,[^/\s]+/[^/,\s]+)*\]))$ + title: Type + description: data type expected on this input glob matching for data type + is allowed + contentSchema: + anyOf: + - type: object + - type: 'null' + title: Contentschema + description: jsonschema of this input/output. Required when type='ref_contentSchema' + fileToKeyMap: + anyOf: + - type: object + - type: 'null' + title: Filetokeymap + description: Place the data associated with the named keys in files + unit: + anyOf: + - type: string + - type: 'null' + title: Unit + description: Units, when it refers to a physical quantity + deprecated: true + defaultValue: + anyOf: + - type: boolean + - type: integer + - type: number + - type: string + - type: 'null' + title: Defaultvalue + deprecated: true + widget: + anyOf: + - $ref: '#/components/schemas/Widget' + - type: 'null' + description: custom widget to use instead of the default one determined + from the data-type + keyId: + type: string + pattern: ^[-_a-zA-Z0-9]+$ + title: Keyid + description: Unique name identifier for this input + additionalProperties: false + type: object + required: + - label + - description + - type + - keyId + title: ServiceInputGet + description: Extends fields of api_schemas_catalog.services.ServiceGet.outputs[*] + example: + defaultValue: 0 + description: Time to wait before completion + displayOrder: 2 + keyId: input_2 + label: Sleep Time + type: number + unit: second + unitLong: seconds + unitShort: sec + widget: + details: + minHeight: 1 + type: TextArea + ServiceKeyVersion: + properties: + key: + type: string + pattern: ^simcore/services/((comp|dynamic|frontend))/([a-z0-9][a-z0-9_.-]*/)*([a-z0-9-_]+[a-z0-9])$ + title: Key + description: distinctive name for the node based on the docker registry + path + version: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Version + description: service version number + type: object + required: + - key + - version + title: ServiceKeyVersion + description: Service `key-version` pair uniquely identifies a service + ServiceOutputGet: + properties: + unitLong: + anyOf: + - type: string + - type: 'null' + title: Unitlong + description: Long name of the unit for display (html-compatible), if available + unitShort: + anyOf: + - type: string + - type: 'null' + title: Unitshort + description: Short name for the unit for display (html-compatible), if available + displayOrder: + anyOf: + - type: number + - type: 'null' + title: Displayorder + description: 'DEPRECATED: new display order is taken from the item position. + This will be removed.' + deprecated: true + label: + type: string + title: Label + description: short name for the property + description: + type: string + title: Description + description: description of the property + type: + type: string + pattern: ^(number|integer|boolean|string|ref_contentSchema|data:([^/\s,]+/[^/\s,]+|\[[^/\s,]+/[^/\s,]+(,[^/\s]+/[^/,\s]+)*\]))$ + title: Type + description: data type expected on this input glob matching for data type + is allowed + contentSchema: + anyOf: + - type: object + - type: 'null' + title: Contentschema + description: jsonschema of this input/output. Required when type='ref_contentSchema' + fileToKeyMap: + anyOf: + - type: object + - type: 'null' + title: Filetokeymap + description: Place the data associated with the named keys in files + unit: + anyOf: + - type: string + - type: 'null' + title: Unit + description: Units, when it refers to a physical quantity + deprecated: true + widget: + anyOf: + - $ref: '#/components/schemas/Widget' + - type: 'null' + description: custom widget to use instead of the default one determined + from the data-type + deprecated: true + keyId: + type: string + pattern: ^[-_a-zA-Z0-9]+$ + title: Keyid + description: Unique name identifier for this input + additionalProperties: false + type: object + required: + - label + - description + - type + - keyId + title: ServiceOutputGet + description: Extends fields of api_schemas_catalog.services.ServiceGet.outputs[*] + example: + description: Time the service waited before completion + displayOrder: 2 + keyId: output_2 + label: Time Slept + type: number + unit: second + unitLong: seconds + unitShort: sec + ServicePricingPlanGet: + properties: + pricingPlanId: + type: integer + exclusiveMinimum: true + title: Pricingplanid + minimum: 0 + displayName: + type: string + title: Displayname + description: + type: string + title: Description + classification: + $ref: '#/components/schemas/PricingPlanClassification' + createdAt: + type: string + format: date-time + title: Createdat + pricingPlanKey: + type: string + title: Pricingplankey + pricingUnits: + items: + $ref: '#/components/schemas/PricingUnitGet' + type: array + title: Pricingunits + type: object + required: + - pricingPlanId + - displayName + - description + - classification + - createdAt + - pricingPlanKey + - pricingUnits + title: ServicePricingPlanGet + ServiceRelease: + properties: + version: + type: string + pattern: ^(0|[1-9]\d*)(\.(0|[1-9]\d*)){2}(-(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*)(\.(0|[1-9]\d*|\d*[-a-zA-Z][-\da-zA-Z]*))*)?(\+[-\da-zA-Z]+(\.[-\da-zA-Z-]+)*)?$ + title: Version + versionDisplay: + anyOf: + - type: string + - type: 'null' + title: Versiondisplay + description: If None, then display `version` + released: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Released + description: When provided, it indicates the release timestamp + retired: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Retired + description: 'whether this service is planned to be retired. If None, the + service is still active. If now Date: Tue, 18 Feb 2025 11:44:36 +0100 Subject: [PATCH 27/63] add user id to data export endpoint --- .../api_schemas_storage/data_export_async_jobs.py | 2 ++ .../src/models_library/storage_schemas.py | 8 ++++++-- .../simcore_service_webserver/storage/_handlers.py | 14 ++++++++++++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py b/packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py index 2c015fdc61b..02162a0b42e 100644 --- a/packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py +++ b/packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py @@ -3,10 +3,12 @@ from common_library.errors_classes import OsparcErrorMixin from models_library.projects_nodes_io import LocationID +from models_library.users import UserID from pydantic import BaseModel, Field class DataExportTaskStartInput(BaseModel): + user_id: UserID location_id: LocationID paths: list[Path] = Field(..., min_length=1) diff --git a/packages/models-library/src/models_library/storage_schemas.py b/packages/models-library/src/models_library/storage_schemas.py index 60b233a2a91..f445990fb29 100644 --- a/packages/models-library/src/models_library/storage_schemas.py +++ b/packages/models-library/src/models_library/storage_schemas.py @@ -385,8 +385,12 @@ class SoftCopyBody(BaseModel): class DataExportPost(BaseModel): paths: list[Path] - def to_storage_model(self, location_id: LocationID) -> DataExportTaskStartInput: - return DataExportTaskStartInput(paths=self.paths, location_id=location_id) + def to_storage_model( + self, user_id: UserID, location_id: LocationID + ) -> DataExportTaskStartInput: + return DataExportTaskStartInput( + paths=self.paths, user_id=user_id, location_id=location_id + ) class AsyncJobGet(BaseModel): diff --git a/services/web/server/src/simcore_service_webserver/storage/_handlers.py b/services/web/server/src/simcore_service_webserver/storage/_handlers.py index 48c05a439d8..ebc4aaaab30 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage/_handlers.py @@ -11,6 +11,7 @@ from aiohttp import ClientTimeout, web from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE from models_library.projects_nodes_io import LocationID +from models_library.rest_base import RequestParameters from models_library.storage_schemas import ( AsyncJobGet, AsyncJobResult, @@ -21,8 +22,9 @@ FileUploadSchema, LinkType, ) +from models_library.users import UserID from models_library.utils.fastapi_encoders import jsonable_encoder -from pydantic import AnyUrl, BaseModel, ByteSize, TypeAdapter +from pydantic import AnyUrl, BaseModel, ByteSize, Field, TypeAdapter from servicelib.aiohttp import status from servicelib.aiohttp.client_session import get_client_session from servicelib.aiohttp.requests_validation import ( @@ -40,6 +42,7 @@ from servicelib.rabbitmq.rpc_interfaces.storage.data_export import start_data_export from servicelib.request_keys import RQT_USERID_KEY from servicelib.rest_responses import unwrap_envelope +from simcore_service_webserver.products._api import RQ_PRODUCT_KEY from simcore_service_webserver.rabbitmq import get_rabbitmq_rpc_client from yarl import URL @@ -385,17 +388,24 @@ class _PathParams(BaseModel): @permission_required("storage.files.*") @handle_data_export_exceptions async def export_data(request: web.Request) -> web.Response: + class _RequestContext(RequestParameters): + user_id: UserID = Field(..., alias=RQT_USERID_KEY) # type: ignore[literal-required] + product_name: str = Field(..., alias=RQ_PRODUCT_KEY) # type: ignore[literal-required] + class _PathParams(BaseModel): location_id: LocationID rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) + _req_ctx = _RequestContext.model_validate(request) _path_params = parse_request_path_parameters_as(_PathParams, request) data_export_post = await parse_request_body_as( model_schema_cls=DataExportPost, request=request ) async_job_rpc_get = await start_data_export( rabbitmq_rpc_client=rabbitmq_rpc_client, - paths=data_export_post.to_storage_model(location_id=_path_params.location_id), + paths=data_export_post.to_storage_model( + user_id=_req_ctx.user_id, location_id=_path_params.location_id + ), ) return create_data_response( AsyncJobGet.from_async_job_rpc_get(async_job_rpc_get), From d614e7e01d266ad98a718ee4960a1a6fe241360e Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Tue, 18 Feb 2025 12:55:28 +0100 Subject: [PATCH 28/63] fix typecheck --- .../server/src/simcore_service_webserver/storage/_handlers.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/storage/_handlers.py b/services/web/server/src/simcore_service_webserver/storage/_handlers.py index ebc4aaaab30..95b2585f6d4 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage/_handlers.py @@ -42,7 +42,6 @@ from servicelib.rabbitmq.rpc_interfaces.storage.data_export import start_data_export from servicelib.request_keys import RQT_USERID_KEY from servicelib.rest_responses import unwrap_envelope -from simcore_service_webserver.products._api import RQ_PRODUCT_KEY from simcore_service_webserver.rabbitmq import get_rabbitmq_rpc_client from yarl import URL @@ -390,7 +389,6 @@ class _PathParams(BaseModel): async def export_data(request: web.Request) -> web.Response: class _RequestContext(RequestParameters): user_id: UserID = Field(..., alias=RQT_USERID_KEY) # type: ignore[literal-required] - product_name: str = Field(..., alias=RQ_PRODUCT_KEY) # type: ignore[literal-required] class _PathParams(BaseModel): location_id: LocationID From db55e7983bdc144306872886ffc91e7652d9265a Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Tue, 18 Feb 2025 13:41:26 +0100 Subject: [PATCH 29/63] fix openapi spec test --- api/specs/web-server/_storage.py | 27 ++++- .../api/v0/openapi.yaml | 108 +++++++++++++++++- .../storage/_handlers.py | 9 +- 3 files changed, 131 insertions(+), 13 deletions(-) diff --git a/api/specs/web-server/_storage.py b/api/specs/web-server/_storage.py index e6eeeacb1b6..96632962922 100644 --- a/api/specs/web-server/_storage.py +++ b/api/specs/web-server/_storage.py @@ -5,12 +5,14 @@ from typing import TypeAlias +from uuid import UUID from fastapi import APIRouter, Query, status from models_library.generics import Envelope from models_library.projects_nodes_io import LocationID from models_library.storage_schemas import ( AsyncJobGet, + AsyncJobResult, AsyncJobStatus, DataExportPost, FileLocation, @@ -174,19 +176,36 @@ async def is_completed_upload_file( # data export @router.post( - "/storage/export-data", + "/storage/locations/{location_id}/export-data", response_model=Envelope[AsyncJobGet], name="storage_export_data", description="Export data", ) -async def export_data(data_export: DataExportPost): +async def export_data(data_export: DataExportPost, location_id: LocationID): """Trigger data export. Returns async job id for getting status and results""" @router.get( - "/storage/async-jobs/status", + "/storage/async-jobs/{job_id}/status", response_model=Envelope[AsyncJobStatus], name="storage_async_job_status", ) -async def get_async_job_status(task_id: AsyncJobGet): +async def get_async_job_status(task_id: AsyncJobGet, job_id: UUID): + """Get async job status""" + + +@router.post( + "/storage/async-jobs/{job_id}:abort", + name="abort_async_job", +) +async def abort_async_job(task_id: AsyncJobGet, job_id: UUID): + """Get async job status""" + + +@router.get( + "/storage/async-jobs/{job_id}/result", + response_model=Envelope[AsyncJobResult], + name="get_async_job_result", +) +async def get_async_job_result(task_id: AsyncJobGet, job_id: UUID): """Get async job status""" 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 4ed1250b114..025b8118a1f 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 @@ -6351,19 +6351,26 @@ paths: application/json: schema: $ref: '#/components/schemas/Envelope_FileUploadCompleteFutureResponse_' - /v0/storage/export-data: + /v0/storage/locations/{location_id}/export-data: post: tags: - storage summary: Storage Export Data description: Export data operationId: export_data + parameters: + - name: location_id + in: path + required: true + schema: + type: integer + title: Location Id requestBody: + required: true content: application/json: schema: $ref: '#/components/schemas/DataExportPost' - required: true responses: '200': description: Successful Response @@ -6371,19 +6378,27 @@ paths: application/json: schema: $ref: '#/components/schemas/Envelope_AsyncJobGet_' - /v0/storage/async-jobs/status: + /v0/storage/async-jobs/{job_id}/status: get: tags: - storage summary: Storage Async Job Status description: Get async job status operationId: get_async_job_status + parameters: + - name: job_id + in: path + required: true + schema: + type: string + format: uuid + title: Job Id requestBody: + required: true content: application/json: schema: $ref: '#/components/schemas/AsyncJobGet' - required: true responses: '200': description: Successful Response @@ -6391,6 +6406,61 @@ paths: application/json: schema: $ref: '#/components/schemas/Envelope_AsyncJobStatus_' + /v0/storage/async-jobs/{job_id}:abort: + post: + tags: + - storage + summary: Abort Async Job + description: Get async job status + operationId: abort_async_job + parameters: + - name: job_id + in: path + required: true + schema: + type: string + format: uuid + title: Job Id + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AsyncJobGet' + responses: + '200': + description: Successful Response + content: + application/json: + schema: {} + /v0/storage/async-jobs/{job_id}/result: + get: + tags: + - storage + summary: Get Async Job Result + description: Get async job status + operationId: get_async_job_result + parameters: + - name: job_id + in: path + required: true + schema: + type: string + format: uuid + title: Job Id + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AsyncJobGet' + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_AsyncJobResult_' /v0/trash:empty: post: tags: @@ -7681,6 +7751,23 @@ components: required: - job_id title: AsyncJobGet + AsyncJobResult: + properties: + result: + anyOf: + - {} + - type: 'null' + title: Result + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + required: + - result + - error + title: AsyncJobResult AsyncJobStatus: properties: job_id: @@ -8503,6 +8590,19 @@ components: title: Error type: object title: Envelope[AsyncJobGet] + Envelope_AsyncJobResult_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/AsyncJobResult' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[AsyncJobResult] Envelope_AsyncJobStatus_: properties: data: diff --git a/services/web/server/src/simcore_service_webserver/storage/_handlers.py b/services/web/server/src/simcore_service_webserver/storage/_handlers.py index 95b2585f6d4..6aac3d06f0e 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage/_handlers.py @@ -380,8 +380,7 @@ class _PathParams(BaseModel): @routes.post( - _storage_locations_prefix + "/{location_id}/export-data", - name="storage_export_data", + _storage_locations_prefix + "/{location_id}/export-data", name="export_data" ) @login_required @permission_required("storage.files.*") @@ -413,7 +412,7 @@ class _PathParams(BaseModel): @routes.get( _storage_prefix + "/async-jobs/{job_id}/status", - name="storage_async_job_status", + name="get_async_job_status", ) @login_required @permission_required("storage.files.*") @@ -435,7 +434,7 @@ async def get_async_job_status(request: web.Request) -> web.Response: @routes.post( _storage_prefix + "/async-jobs/{job_id}:abort", - name="storage_async_job_abort", + name="abort_async_job", ) @login_required @permission_required("storage.files.*") @@ -457,7 +456,7 @@ async def abort_async_job(request: web.Request) -> web.Response: @routes.get( _storage_prefix + "/async-jobs/{job_id}/result", - name="storage_async_job_result", + name="get_async_job_result", ) @login_required @permission_required("storage.files.*") From 1718df8ef40a608faeb2b8529c8694f9eb069324 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Tue, 18 Feb 2025 14:26:43 +0100 Subject: [PATCH 30/63] @sanderegg fix absolute imports --- .../src/models_library/storage_schemas.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/models-library/src/models_library/storage_schemas.py b/packages/models-library/src/models_library/storage_schemas.py index f445990fb29..e0548973256 100644 --- a/packages/models-library/src/models_library/storage_schemas.py +++ b/packages/models-library/src/models_library/storage_schemas.py @@ -14,14 +14,6 @@ from typing import Annotated, Any, Literal, Self, TypeAlias from uuid import UUID -from models_library.api_schemas_rpc_async_jobs.async_jobs import ( - AsyncJobRpcGet, - AsyncJobRpcResult, - AsyncJobRpcStatus, -) -from models_library.api_schemas_storage.data_export_async_jobs import ( - DataExportTaskStartInput, -) from models_library.projects import ProjectID from models_library.users import UserID from pydantic import ( @@ -38,6 +30,12 @@ ) from pydantic.networks import AnyUrl +from .api_schemas_rpc_async_jobs.async_jobs import ( + AsyncJobRpcGet, + AsyncJobRpcResult, + AsyncJobRpcStatus, +) +from .api_schemas_storage.data_export_async_jobs import DataExportTaskStartInput from .basic_regex import DATCORE_DATASET_NAME_RE, S3_BUCKET_NAME_RE from .basic_types import SHA256Str from .generics import ListModel From 7faf55fd34af0b851d36d7faafad593fe05e5a35 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Tue, 18 Feb 2025 14:31:46 +0100 Subject: [PATCH 31/63] @sanderegg @pcrespov remove 'rpc' from classnames --- .../api_schemas_rpc_async_jobs/async_jobs.py | 16 +++++----- .../src/models_library/storage_schemas.py | 12 ++++---- .../rpc_interfaces/async_jobs/async_jobs.py | 26 ++++++++-------- .../rpc_interfaces/storage/data_export.py | 6 ++-- .../api/rpc/_async_jobs.py | 20 ++++++------- .../api/rpc/_data_export.py | 6 ++-- .../storage/tests/unit/test_data_export.py | 24 +++++++-------- .../unit/with_dbs/03/test_storage_rpc.py | 30 +++++++++---------- 8 files changed, 70 insertions(+), 70 deletions(-) diff --git a/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py b/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py index 97714f3fea5..cd4d593317b 100644 --- a/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py +++ b/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py @@ -5,11 +5,11 @@ from pydantic import BaseModel, Field, PositiveFloat, model_validator from typing_extensions import Self -AsyncJobRpcId: TypeAlias = UUID +AsyncJobId: TypeAlias = UUID -class AsyncJobRpcStatus(BaseModel): - job_id: AsyncJobRpcId +class AsyncJobStatus(BaseModel): + job_id: AsyncJobId task_progress: PositiveFloat = Field(..., ge=0.0, le=1.0) done: bool started: datetime @@ -26,16 +26,16 @@ def _check_consistency(self) -> Self: return self -class AsyncJobRpcResult(BaseModel): +class AsyncJobResult(BaseModel): result: Any | None error: Any | None -class AsyncJobRpcGet(BaseModel): - job_id: AsyncJobRpcId +class AsyncJobGet(BaseModel): + job_id: AsyncJobId task_name: str -class AsyncJobRpcAbort(BaseModel): +class AsyncJobAbort(BaseModel): result: bool - job_id: AsyncJobRpcId + job_id: AsyncJobId diff --git a/packages/models-library/src/models_library/storage_schemas.py b/packages/models-library/src/models_library/storage_schemas.py index e0548973256..c163e64291e 100644 --- a/packages/models-library/src/models_library/storage_schemas.py +++ b/packages/models-library/src/models_library/storage_schemas.py @@ -31,9 +31,9 @@ from pydantic.networks import AnyUrl from .api_schemas_rpc_async_jobs.async_jobs import ( - AsyncJobRpcGet, - AsyncJobRpcResult, - AsyncJobRpcStatus, + AsyncJobGet, + AsyncJobResult, + AsyncJobStatus, ) from .api_schemas_storage.data_export_async_jobs import DataExportTaskStartInput from .basic_regex import DATCORE_DATASET_NAME_RE, S3_BUCKET_NAME_RE @@ -395,7 +395,7 @@ class AsyncJobGet(BaseModel): job_id: UUID @classmethod - def from_async_job_rpc_get(cls, async_job_rpc_get: AsyncJobRpcGet) -> "AsyncJobGet": + def from_async_job_rpc_get(cls, async_job_rpc_get: AsyncJobGet) -> "AsyncJobGet": return AsyncJobGet(job_id=async_job_rpc_get.job_id) @@ -408,7 +408,7 @@ class AsyncJobStatus(BaseModel): @classmethod def from_async_job_rpc_status( - cls, async_job_rpc_status: AsyncJobRpcStatus + cls, async_job_rpc_status: AsyncJobStatus ) -> "AsyncJobStatus": return AsyncJobStatus( job_id=async_job_rpc_status.job_id, @@ -425,7 +425,7 @@ class AsyncJobResult(BaseModel): @classmethod def from_async_job_rpc_result( - cls, async_job_rpc_result: AsyncJobRpcResult + cls, async_job_rpc_result: AsyncJobResult ) -> "AsyncJobResult": return AsyncJobResult( result=async_job_rpc_result.result, error=async_job_rpc_result.error diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py index f922976505a..807c6ed0640 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py @@ -1,10 +1,10 @@ from typing import Final from models_library.api_schemas_rpc_async_jobs.async_jobs import ( - AsyncJobRpcAbort, - AsyncJobRpcId, - AsyncJobRpcResult, - AsyncJobRpcStatus, + AsyncJobAbort, + AsyncJobId, + AsyncJobResult, + AsyncJobStatus, ) from models_library.rabbitmq_basic_types import RPCMethodName, RPCNamespace from pydantic import NonNegativeInt, TypeAdapter @@ -20,15 +20,15 @@ async def abort( rabbitmq_rpc_client: RabbitMQRPCClient, *, rpc_namespace: RPCNamespace, - job_id: AsyncJobRpcId -) -> AsyncJobRpcAbort: + job_id: AsyncJobId +) -> AsyncJobAbort: result = await rabbitmq_rpc_client.request( rpc_namespace, _RPC_METHOD_NAME_ADAPTER.validate_python("abort"), job_id=job_id, timeout_s=_DEFAULT_TIMEOUT_S, ) - assert isinstance(result, AsyncJobRpcAbort) + assert isinstance(result, AsyncJobAbort) return result @@ -36,15 +36,15 @@ async def get_status( rabbitmq_rpc_client: RabbitMQRPCClient, *, rpc_namespace: RPCNamespace, - job_id: AsyncJobRpcId -) -> AsyncJobRpcStatus: + job_id: AsyncJobId +) -> AsyncJobStatus: result = await rabbitmq_rpc_client.request( rpc_namespace, _RPC_METHOD_NAME_ADAPTER.validate_python("get_status"), job_id=job_id, timeout_s=_DEFAULT_TIMEOUT_S, ) - assert isinstance(result, AsyncJobRpcStatus) + assert isinstance(result, AsyncJobStatus) return result @@ -52,13 +52,13 @@ async def get_result( rabbitmq_rpc_client: RabbitMQRPCClient, *, rpc_namespace: RPCNamespace, - job_id: AsyncJobRpcId -) -> AsyncJobRpcResult: + job_id: AsyncJobId +) -> AsyncJobResult: result = await rabbitmq_rpc_client.request( rpc_namespace, _RPC_METHOD_NAME_ADAPTER.validate_python("get_result"), job_id=job_id, timeout_s=_DEFAULT_TIMEOUT_S, ) - assert isinstance(result, AsyncJobRpcResult) + assert isinstance(result, AsyncJobResult) return result diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py index 7c8f2530682..1b773fbdec9 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py @@ -1,6 +1,6 @@ from typing import Final -from models_library.api_schemas_rpc_async_jobs.async_jobs import AsyncJobRpcGet +from models_library.api_schemas_rpc_async_jobs.async_jobs import AsyncJobGet from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE from models_library.api_schemas_storage.data_export_async_jobs import ( DataExportTaskStartInput, @@ -17,12 +17,12 @@ async def start_data_export( rabbitmq_rpc_client: RabbitMQRPCClient, *, paths: DataExportTaskStartInput -) -> AsyncJobRpcGet: +) -> AsyncJobGet: result = await rabbitmq_rpc_client.request( STORAGE_RPC_NAMESPACE, _RPC_METHOD_NAME_ADAPTER.validate_python("start_data_export"), paths=paths, timeout_s=_DEFAULT_TIMEOUT_S, ) - assert isinstance(result, AsyncJobRpcGet) + assert isinstance(result, AsyncJobGet) return result diff --git a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py index b008720e579..f6f205ac05f 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py @@ -2,10 +2,10 @@ from fastapi import FastAPI from models_library.api_schemas_rpc_async_jobs.async_jobs import ( - AsyncJobRpcAbort, - AsyncJobRpcId, - AsyncJobRpcResult, - AsyncJobRpcStatus, + AsyncJobAbort, + AsyncJobId, + AsyncJobResult, + AsyncJobStatus, ) from models_library.api_schemas_rpc_async_jobs.exceptions import ( ResultError, @@ -17,15 +17,15 @@ @router.expose() -async def abort(app: FastAPI, job_id: AsyncJobRpcId) -> AsyncJobRpcAbort: +async def abort(app: FastAPI, job_id: AsyncJobId) -> AsyncJobAbort: assert app # nosec - return AsyncJobRpcAbort(result=True, job_id=job_id) + return AsyncJobAbort(result=True, job_id=job_id) @router.expose(reraise_if_error_type=(StatusError,)) -async def get_status(app: FastAPI, job_id: AsyncJobRpcId) -> AsyncJobRpcStatus: +async def get_status(app: FastAPI, job_id: AsyncJobId) -> AsyncJobStatus: assert app # nosec - return AsyncJobRpcStatus( + return AsyncJobStatus( job_id=job_id, task_progress=0.5, done=False, @@ -35,7 +35,7 @@ async def get_status(app: FastAPI, job_id: AsyncJobRpcId) -> AsyncJobRpcStatus: @router.expose(reraise_if_error_type=(ResultError,)) -async def get_result(app: FastAPI, job_id: AsyncJobRpcId) -> AsyncJobRpcResult: +async def get_result(app: FastAPI, job_id: AsyncJobId) -> AsyncJobResult: assert app # nosec assert job_id # nosec - return AsyncJobRpcResult(result="Here's your result.", error=None) + return AsyncJobResult(result="Here's your result.", error=None) diff --git a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py index 42d05d587f0..8dc6a033d88 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py @@ -1,7 +1,7 @@ from uuid import uuid4 from fastapi import FastAPI -from models_library.api_schemas_rpc_async_jobs.async_jobs import AsyncJobRpcGet +from models_library.api_schemas_rpc_async_jobs.async_jobs import AsyncJobGet from models_library.api_schemas_storage.data_export_async_jobs import ( AccessRightError, DataExportError, @@ -22,9 +22,9 @@ ) async def start_data_export( app: FastAPI, paths: DataExportTaskStartInput -) -> AsyncJobRpcGet: +) -> AsyncJobGet: assert app # nosec - return AsyncJobRpcGet( + return AsyncJobGet( job_id=uuid4(), task_name=", ".join(str(p) for p in paths.paths), ) diff --git a/services/storage/tests/unit/test_data_export.py b/services/storage/tests/unit/test_data_export.py index 56a71c0b39b..a7912a60c30 100644 --- a/services/storage/tests/unit/test_data_export.py +++ b/services/storage/tests/unit/test_data_export.py @@ -7,11 +7,11 @@ from faker import Faker from fastapi import FastAPI from models_library.api_schemas_rpc_async_jobs.async_jobs import ( - AsyncJobRpcAbort, - AsyncJobRpcGet, - AsyncJobRpcId, - AsyncJobRpcResult, - AsyncJobRpcStatus, + AsyncJobAbort, + AsyncJobGet, + AsyncJobId, + AsyncJobResult, + AsyncJobStatus, ) from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE from models_library.api_schemas_storage.data_export_async_jobs import ( @@ -80,30 +80,30 @@ async def test_start_data_export(rpc_client: RabbitMQRPCClient, faker: Faker): rpc_client, paths=DataExportTaskStartInput(location_id=0, paths=[Path(faker.file_path())]), ) - assert isinstance(result, AsyncJobRpcGet) + assert isinstance(result, AsyncJobGet) async def test_abort_data_export(rpc_client: RabbitMQRPCClient, faker: Faker): - _job_id = AsyncJobRpcId(faker.uuid4()) + _job_id = AsyncJobId(faker.uuid4()) result = await async_jobs.abort( rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, job_id=_job_id ) - assert isinstance(result, AsyncJobRpcAbort) + assert isinstance(result, AsyncJobAbort) assert result.job_id == _job_id async def test_get_data_export_status(rpc_client: RabbitMQRPCClient, faker: Faker): - _job_id = AsyncJobRpcId(faker.uuid4()) + _job_id = AsyncJobId(faker.uuid4()) result = await async_jobs.get_status( rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, job_id=_job_id ) - assert isinstance(result, AsyncJobRpcStatus) + assert isinstance(result, AsyncJobStatus) assert result.job_id == _job_id async def test_get_data_export_result(rpc_client: RabbitMQRPCClient, faker: Faker): - _job_id = AsyncJobRpcId(faker.uuid4()) + _job_id = AsyncJobId(faker.uuid4()) result = await async_jobs.get_result( rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, job_id=_job_id ) - assert isinstance(result, AsyncJobRpcResult) + assert isinstance(result, AsyncJobResult) diff --git a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py index e39077ae243..ec8a7af1ede 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py +++ b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py @@ -6,11 +6,11 @@ from aiohttp.test_utils import TestClient from faker import Faker from models_library.api_schemas_rpc_async_jobs.async_jobs import ( - AsyncJobRpcAbort, - AsyncJobRpcGet, - AsyncJobRpcId, - AsyncJobRpcResult, - AsyncJobRpcStatus, + AsyncJobAbort, + AsyncJobGet, + AsyncJobId, + AsyncJobResult, + AsyncJobStatus, ) from models_library.api_schemas_rpc_async_jobs.exceptions import ( ResultError, @@ -57,7 +57,7 @@ def side_effect(*args, **kwargs): @pytest.mark.parametrize( "backend_result_or_exception", [ - AsyncJobRpcGet(job_id=AsyncJobRpcId(_faker.uuid4()), task_name=_faker.text()), + AsyncJobGet(job_id=AsyncJobId(_faker.uuid4()), task_name=_faker.text()), InvalidFileIdentifierError(file_id=Path("/my/file")), AccessRightError(user_id=_faker.pyint(min_value=0), file_id=Path("/my/file")), DataExportError(job_id=_faker.pyint(min_value=0)), @@ -81,7 +81,7 @@ async def test_data_export( response = await client.post( "/v0/storage/locations/0/export-data", data=_body.model_dump_json() ) - if isinstance(backend_result_or_exception, AsyncJobRpcGet): + if isinstance(backend_result_or_exception, AsyncJobGet): assert response.status == status.HTTP_202_ACCEPTED Envelope[AsyncJobGet].model_validate(await response.json()) elif isinstance(backend_result_or_exception, InvalidFileIdentifierError): @@ -97,7 +97,7 @@ async def test_data_export( @pytest.mark.parametrize( "backend_result_or_exception", [ - AsyncJobRpcStatus( + AsyncJobStatus( job_id=_faker.uuid4(), task_progress=0.5, done=False, @@ -115,11 +115,11 @@ async def test_get_async_jobs_status( create_storage_rpc_client_mock: Callable[[str, Any], None], backend_result_or_exception: Any, ): - _job_id = AsyncJobRpcId(_faker.uuid4()) + _job_id = AsyncJobId(_faker.uuid4()) create_storage_rpc_client_mock(get_status.__name__, backend_result_or_exception) response = await client.get(f"/v0/storage/async-jobs/{_job_id}/status") - if isinstance(backend_result_or_exception, AsyncJobRpcStatus): + if isinstance(backend_result_or_exception, AsyncJobStatus): assert response.status == status.HTTP_200_OK response_body_data = ( Envelope[AsyncJobGet].model_validate(await response.json()).data @@ -141,9 +141,9 @@ async def test_abort_async_jobs( faker: Faker, abort_success: bool, ): - _job_id = AsyncJobRpcId(faker.uuid4()) + _job_id = AsyncJobId(faker.uuid4()) create_storage_rpc_client_mock( - abort.__name__, AsyncJobRpcAbort(result=abort_success, job_id=_job_id) + abort.__name__, AsyncJobAbort(result=abort_success, job_id=_job_id) ) response = await client.post(f"/v0/storage/async-jobs/{_job_id}:abort") @@ -158,7 +158,7 @@ async def test_abort_async_jobs( @pytest.mark.parametrize( "backend_result_or_exception", [ - AsyncJobRpcResult(result=None, error=_faker.text()), + AsyncJobResult(result=None, error=_faker.text()), ResultError(job_id=_faker.uuid4()), ], ids=lambda x: type(x).__name__, @@ -171,12 +171,12 @@ async def test_get_async_job_result( faker: Faker, backend_result_or_exception: Any, ): - _job_id = AsyncJobRpcId(faker.uuid4()) + _job_id = AsyncJobId(faker.uuid4()) create_storage_rpc_client_mock(get_result.__name__, backend_result_or_exception) response = await client.get(f"/v0/storage/async-jobs/{_job_id}/result") - if isinstance(backend_result_or_exception, AsyncJobRpcResult): + if isinstance(backend_result_or_exception, AsyncJobResult): assert response.status == status.HTTP_200_OK elif isinstance(backend_result_or_exception, ResultError): assert response.status == status.HTTP_500_INTERNAL_SERVER_ERROR From 434602f962a9b9f18f50905661cf1a3c0903c2d9 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Tue, 18 Feb 2025 14:58:22 +0100 Subject: [PATCH 32/63] task_progress -> progress --- .../src/models_library/api_schemas_rpc_async_jobs/async_jobs.py | 2 +- .../storage/src/simcore_service_storage/api/rpc/_async_jobs.py | 2 +- services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py b/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py index cd4d593317b..c708730051d 100644 --- a/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py +++ b/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py @@ -10,7 +10,7 @@ class AsyncJobStatus(BaseModel): job_id: AsyncJobId - task_progress: PositiveFloat = Field(..., ge=0.0, le=1.0) + progress: PositiveFloat = Field(..., ge=0.0, le=1.0) done: bool started: datetime stopped: datetime | None diff --git a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py index f6f205ac05f..2ea1a4dc495 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py @@ -27,7 +27,7 @@ async def get_status(app: FastAPI, job_id: AsyncJobId) -> AsyncJobStatus: assert app # nosec return AsyncJobStatus( job_id=job_id, - task_progress=0.5, + progress=0.5, done=False, started=datetime.now(), stopped=None, diff --git a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py index ec8a7af1ede..daf38c5906b 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py +++ b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py @@ -99,7 +99,7 @@ async def test_data_export( [ AsyncJobStatus( job_id=_faker.uuid4(), - task_progress=0.5, + progress=0.5, done=False, started=datetime.now(), stopped=None, From fd777ab9a26992a63dd3b84c680a1dc3e9c9f62a Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Tue, 18 Feb 2025 15:15:36 +0100 Subject: [PATCH 33/63] rename storage schemas exposed via webserver --- .../api_schemas_rpc_async_jobs/async_jobs.py | 5 +- .../api_schemas_webserver/storage.py | 70 +++++++++++++++++++ .../src/models_library/storage_schemas.py | 60 ---------------- .../storage/_handlers.py | 22 +++--- 4 files changed, 85 insertions(+), 72 deletions(-) create mode 100644 packages/models-library/src/models_library/api_schemas_webserver/storage.py diff --git a/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py b/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py index c708730051d..418a709f080 100644 --- a/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py +++ b/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py @@ -2,7 +2,8 @@ from typing import Any, TypeAlias from uuid import UUID -from pydantic import BaseModel, Field, PositiveFloat, model_validator +from pydantic import BaseModel, model_validator +from servicelib.progress_bar import ProgressBarData from typing_extensions import Self AsyncJobId: TypeAlias = UUID @@ -10,7 +11,7 @@ class AsyncJobStatus(BaseModel): job_id: AsyncJobId - progress: PositiveFloat = Field(..., ge=0.0, le=1.0) + progress: ProgressBarData done: bool started: datetime stopped: datetime | None diff --git a/packages/models-library/src/models_library/api_schemas_webserver/storage.py b/packages/models-library/src/models_library/api_schemas_webserver/storage.py new file mode 100644 index 00000000000..aae7182744c --- /dev/null +++ b/packages/models-library/src/models_library/api_schemas_webserver/storage.py @@ -0,0 +1,70 @@ +from datetime import datetime +from pathlib import Path +from typing import Any + +from pydantic import BaseModel + +from ..api_schemas_rpc_async_jobs.async_jobs import ( + AsyncJobGet, + AsyncJobId, + AsyncJobResult, + AsyncJobStatus, + ProgressBarData, +) +from ..api_schemas_storage.data_export_async_jobs import DataExportTaskStartInput +from ..projects_nodes_io import LocationID +from ..users import UserID + + +class DataExportPost(BaseModel): + paths: list[Path] + + def to_storage_model( + self, user_id: UserID, location_id: LocationID + ) -> DataExportTaskStartInput: + return DataExportTaskStartInput( + paths=self.paths, user_id=user_id, location_id=location_id + ) + + +class StorageAsyncJobGet(BaseModel): + job_id: AsyncJobId + + @classmethod + def from_async_job_rpc_get( + cls, async_job_rpc_get: AsyncJobGet + ) -> "StorageAsyncJobGet": + return StorageAsyncJobGet(job_id=async_job_rpc_get.job_id) + + +class StorageAsyncJobStatus(BaseModel): + job_id: AsyncJobId + progress: ProgressBarData + done: bool + started: datetime + stopped: datetime | None + + @classmethod + def from_async_job_rpc_status( + cls, async_job_rpc_status: AsyncJobStatus + ) -> "StorageAsyncJobStatus": + return StorageAsyncJobStatus( + job_id=async_job_rpc_status.job_id, + progress=async_job_rpc_status.progress, + done=async_job_rpc_status.done, + started=async_job_rpc_status.started, + stopped=async_job_rpc_status.stopped, + ) + + +class StorageAsyncJobResult(BaseModel): + result: Any | None + error: Any | None + + @classmethod + def from_async_job_rpc_result( + cls, async_job_rpc_result: AsyncJobResult + ) -> "StorageAsyncJobResult": + return StorageAsyncJobResult( + result=async_job_rpc_result.result, error=async_job_rpc_result.error + ) diff --git a/packages/models-library/src/models_library/storage_schemas.py b/packages/models-library/src/models_library/storage_schemas.py index c163e64291e..aa56821bde2 100644 --- a/packages/models-library/src/models_library/storage_schemas.py +++ b/packages/models-library/src/models_library/storage_schemas.py @@ -10,7 +10,6 @@ from enum import Enum # /data-export -from pathlib import Path from typing import Annotated, Any, Literal, Self, TypeAlias from uuid import UUID @@ -21,7 +20,6 @@ ByteSize, ConfigDict, Field, - PositiveFloat, PositiveInt, RootModel, StringConstraints, @@ -30,12 +28,6 @@ ) from pydantic.networks import AnyUrl -from .api_schemas_rpc_async_jobs.async_jobs import ( - AsyncJobGet, - AsyncJobResult, - AsyncJobStatus, -) -from .api_schemas_storage.data_export_async_jobs import DataExportTaskStartInput from .basic_regex import DATCORE_DATASET_NAME_RE, S3_BUCKET_NAME_RE from .basic_types import SHA256Str from .generics import ListModel @@ -378,55 +370,3 @@ def ensure_consistent_entries(self: Self) -> Self: class SoftCopyBody(BaseModel): link_id: SimcoreS3FileID - - -class DataExportPost(BaseModel): - paths: list[Path] - - def to_storage_model( - self, user_id: UserID, location_id: LocationID - ) -> DataExportTaskStartInput: - return DataExportTaskStartInput( - paths=self.paths, user_id=user_id, location_id=location_id - ) - - -class AsyncJobGet(BaseModel): - job_id: UUID - - @classmethod - def from_async_job_rpc_get(cls, async_job_rpc_get: AsyncJobGet) -> "AsyncJobGet": - return AsyncJobGet(job_id=async_job_rpc_get.job_id) - - -class AsyncJobStatus(BaseModel): - job_id: UUID - task_progress: PositiveFloat = Field(..., ge=0.0, le=1.0) - done: bool - started: datetime - stopped: datetime | None - - @classmethod - def from_async_job_rpc_status( - cls, async_job_rpc_status: AsyncJobStatus - ) -> "AsyncJobStatus": - return AsyncJobStatus( - job_id=async_job_rpc_status.job_id, - task_progress=async_job_rpc_status.task_progress, - done=async_job_rpc_status.done, - started=async_job_rpc_status.started, - stopped=async_job_rpc_status.stopped, - ) - - -class AsyncJobResult(BaseModel): - result: Any | None - error: Any | None - - @classmethod - def from_async_job_rpc_result( - cls, async_job_rpc_result: AsyncJobResult - ) -> "AsyncJobResult": - return AsyncJobResult( - result=async_job_rpc_result.result, error=async_job_rpc_result.error - ) diff --git a/services/web/server/src/simcore_service_webserver/storage/_handlers.py b/services/web/server/src/simcore_service_webserver/storage/_handlers.py index 6aac3d06f0e..a43f7629d96 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage/_handlers.py @@ -10,13 +10,15 @@ from aiohttp import ClientTimeout, web from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE +from models_library.api_schemas_webserver.storage import ( + DataExportPost, + StorageAsyncJobGet, + StorageAsyncJobResult, + StorageAsyncJobStatus, +) from models_library.projects_nodes_io import LocationID from models_library.rest_base import RequestParameters from models_library.storage_schemas import ( - AsyncJobGet, - AsyncJobResult, - AsyncJobStatus, - DataExportPost, FileUploadCompleteResponse, FileUploadCompletionBody, FileUploadSchema, @@ -405,7 +407,7 @@ class _PathParams(BaseModel): ), ) return create_data_response( - AsyncJobGet.from_async_job_rpc_get(async_job_rpc_get), + StorageAsyncJobGet.from_async_job_rpc_get(async_job_rpc_get), status=status.HTTP_202_ACCEPTED, ) @@ -420,14 +422,14 @@ class _PathParams(BaseModel): async def get_async_job_status(request: web.Request) -> web.Response: rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) - async_job_get = parse_request_path_parameters_as(AsyncJobGet, request) + async_job_get = parse_request_path_parameters_as(StorageAsyncJobGet, request) async_job_rpc_status = await get_status( rabbitmq_rpc_client=rabbitmq_rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, job_id=async_job_get.job_id, ) return create_data_response( - AsyncJobStatus.from_async_job_rpc_status(async_job_rpc_status), + StorageAsyncJobStatus.from_async_job_rpc_status(async_job_rpc_status), status=status.HTTP_200_OK, ) @@ -441,7 +443,7 @@ async def get_async_job_status(request: web.Request) -> web.Response: @handle_data_export_exceptions async def abort_async_job(request: web.Request) -> web.Response: rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) - async_job_get = parse_request_path_parameters_as(AsyncJobGet, request) + async_job_get = parse_request_path_parameters_as(StorageAsyncJobGet, request) async_job_rpc_abort = await abort( rabbitmq_rpc_client=rabbitmq_rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, @@ -463,13 +465,13 @@ async def abort_async_job(request: web.Request) -> web.Response: @handle_data_export_exceptions async def get_async_job_result(request: web.Request) -> web.Response: rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) - async_job_get = parse_request_path_parameters_as(AsyncJobGet, request) + async_job_get = parse_request_path_parameters_as(StorageAsyncJobGet, request) async_job_rpc_result = await get_result( rabbitmq_rpc_client=rabbitmq_rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, job_id=async_job_get.job_id, ) return create_data_response( - AsyncJobResult.from_async_job_rpc_result(async_job_rpc_result), + StorageAsyncJobResult.from_async_job_rpc_result(async_job_rpc_result), status=status.HTTP_200_OK, ) From 400a6c173775a9bc8e8457bfbc8f022e41efaee0 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Tue, 18 Feb 2025 15:24:16 +0100 Subject: [PATCH 34/63] use ProgressReport --- .../models_library/api_schemas_rpc_async_jobs/async_jobs.py | 5 +++-- .../src/models_library/api_schemas_webserver/storage.py | 4 ++-- .../src/simcore_service_storage/api/rpc/_async_jobs.py | 4 +++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py b/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py index 418a709f080..d1b60ea032c 100644 --- a/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py +++ b/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py @@ -3,15 +3,16 @@ from uuid import UUID from pydantic import BaseModel, model_validator -from servicelib.progress_bar import ProgressBarData from typing_extensions import Self +from ..progress_bar import ProgressReport + AsyncJobId: TypeAlias = UUID class AsyncJobStatus(BaseModel): job_id: AsyncJobId - progress: ProgressBarData + progress: ProgressReport done: bool started: datetime stopped: datetime | None diff --git a/packages/models-library/src/models_library/api_schemas_webserver/storage.py b/packages/models-library/src/models_library/api_schemas_webserver/storage.py index aae7182744c..2e768afc9af 100644 --- a/packages/models-library/src/models_library/api_schemas_webserver/storage.py +++ b/packages/models-library/src/models_library/api_schemas_webserver/storage.py @@ -9,9 +9,9 @@ AsyncJobId, AsyncJobResult, AsyncJobStatus, - ProgressBarData, ) from ..api_schemas_storage.data_export_async_jobs import DataExportTaskStartInput +from ..progress_bar import ProgressReport from ..projects_nodes_io import LocationID from ..users import UserID @@ -39,7 +39,7 @@ def from_async_job_rpc_get( class StorageAsyncJobStatus(BaseModel): job_id: AsyncJobId - progress: ProgressBarData + progress: ProgressReport done: bool started: datetime stopped: datetime | None diff --git a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py index 2ea1a4dc495..6a0fff100b0 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py @@ -11,6 +11,7 @@ ResultError, StatusError, ) +from models_library.progress_bar import ProgressReport from servicelib.rabbitmq import RPCRouter router = RPCRouter() @@ -25,9 +26,10 @@ async def abort(app: FastAPI, job_id: AsyncJobId) -> AsyncJobAbort: @router.expose(reraise_if_error_type=(StatusError,)) async def get_status(app: FastAPI, job_id: AsyncJobId) -> AsyncJobStatus: assert app # nosec + progress_report = ProgressReport(actual_value=0.5, total=1.0, attempt=1) return AsyncJobStatus( job_id=job_id, - progress=0.5, + progress=progress_report, done=False, started=datetime.now(), stopped=None, From 3383265b08e1070e56fb60299784e20bda5ac285 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Tue, 18 Feb 2025 15:43:41 +0100 Subject: [PATCH 35/63] restructuring --- api/specs/web-server/_storage.py | 6 +++--- .../aws-library/src/aws_library/s3/_client.py | 6 +++++- .../aws-library/src/aws_library/s3/_models.py | 2 +- packages/aws-library/tests/test_s3_client.py | 5 ++++- .../rest}/storage_schemas.py | 8 ++++---- .../{ => rpc}/data_export_async_jobs.py | 0 .../api_schemas_webserver/storage.py | 2 +- .../src/pytest_simcore/helpers/s3.py | 6 +++++- .../services_api_mocks_for_aiohttp_clients.py | 8 ++++---- .../rpc_interfaces/storage/data_export.py | 2 +- .../node_ports_common/_filemanager_utils.py | 6 +++--- .../node_ports_common/file_io_utils.py | 6 +++++- .../node_ports_common/filemanager.py | 6 +++--- .../node_ports_common/storage_client.py | 8 ++++---- .../src/simcore_sdk/node_ports_v2/__init__.py | 4 +++- .../simcore_sdk/node_ports_v2/nodeports_v2.py | 2 +- .../src/simcore_sdk/node_ports_v2/port.py | 2 +- .../simcore_sdk/node_ports_v2/port_utils.py | 5 ++++- .../simcore-sdk/tests/integration/conftest.py | 2 +- .../test_node_ports_common_file_io_utils.py | 2 +- .../tests/unit/test_node_ports_v2_port.py | 2 +- .../tests/unit/test_storage_client.py | 4 ++-- .../api/routes/files.py | 6 +++++- .../models/schemas/files.py | 2 +- .../services_http/storage.py | 11 ++++++++--- services/api-server/tests/unit/conftest.py | 2 +- .../api-server/tests/unit/test_api_files.py | 6 +++++- .../tests/unit/test_models_schemas_files.py | 4 +++- .../tests/unit/with_dbs/test_utils_dask.py | 5 ++++- .../test_modules_long_running_tasks.py | 2 +- .../services/modules/db/service_runs_db.py | 2 +- .../services/modules/s3.py | 2 +- .../services/service_runs.py | 2 +- .../api/rest/_datasets.py | 5 ++++- .../simcore_service_storage/api/rest/_files.py | 6 +++--- .../api/rest/_health.py | 5 ++++- .../api/rest/_locations.py | 2 +- .../api/rest/_simcore_s3.py | 5 ++++- .../api/rpc/_data_export.py | 2 +- .../src/simcore_service_storage/constants.py | 2 +- .../src/simcore_service_storage/datcore_dsm.py | 6 +++++- .../src/simcore_service_storage/dsm_factory.py | 5 ++++- .../src/simcore_service_storage/models.py | 18 +++++++++--------- .../modules/datcore_adapter/datcore_adapter.py | 2 +- .../simcore_service_storage/simcore_s3_dsm.py | 14 +++++++------- .../utils/simcore_s3_dsm_utils.py | 2 +- services/storage/tests/conftest.py | 10 +++++----- .../storage/tests/unit/test_data_export.py | 6 ++++-- .../storage/tests/unit/test_dsm_dsmcleaner.py | 2 +- .../storage/tests/unit/test_dsm_soft_links.py | 2 +- .../tests/unit/test_handlers_datasets.py | 5 ++++- .../storage/tests/unit/test_handlers_files.py | 8 ++++---- .../tests/unit/test_handlers_files_metadata.py | 5 ++++- .../storage/tests/unit/test_handlers_health.py | 5 ++++- .../tests/unit/test_handlers_simcore_s3.py | 5 ++++- services/storage/tests/unit/test_models.py | 2 +- .../storage/tests/unit/test_simcore_s3_dsm.py | 2 +- services/storage/tests/unit/test_utils.py | 2 +- .../projects/_nodes_api.py | 2 +- .../storage/_exception_handlers.py | 2 +- .../storage/_handlers.py | 12 ++++++------ .../simcore_service_webserver/storage/api.py | 8 ++++---- .../unit/isolated/test_projects__nodes_api.py | 2 +- .../tests/unit/with_dbs/01/test_storage.py | 8 ++++---- .../with_dbs/02/test_projects_nodes_handler.py | 5 ++++- .../unit/with_dbs/03/test_storage_handlers.py | 2 +- .../tests/unit/with_dbs/03/test_storage_rpc.py | 14 +++++++++----- 67 files changed, 198 insertions(+), 123 deletions(-) rename packages/models-library/src/models_library/{ => api_schemas_storage/rest}/storage_schemas.py (98%) rename packages/models-library/src/models_library/api_schemas_storage/{ => rpc}/data_export_async_jobs.py (100%) diff --git a/api/specs/web-server/_storage.py b/api/specs/web-server/_storage.py index 96632962922..86d3358f04a 100644 --- a/api/specs/web-server/_storage.py +++ b/api/specs/web-server/_storage.py @@ -8,9 +8,7 @@ from uuid import UUID from fastapi import APIRouter, Query, status -from models_library.generics import Envelope -from models_library.projects_nodes_io import LocationID -from models_library.storage_schemas import ( +from models_library.api_schemas_storage.rest.storage_schemas import ( AsyncJobGet, AsyncJobResult, AsyncJobStatus, @@ -24,6 +22,8 @@ LinkType, PresignedLink, ) +from models_library.generics import Envelope +from models_library.projects_nodes_io import LocationID from pydantic import AnyUrl, ByteSize from simcore_service_webserver._meta import API_VTAG from simcore_service_webserver.storage.schemas import DatasetMetaData, FileMetaData diff --git a/packages/aws-library/src/aws_library/s3/_client.py b/packages/aws-library/src/aws_library/s3/_client.py index ec504df7444..55483ff546f 100644 --- a/packages/aws-library/src/aws_library/s3/_client.py +++ b/packages/aws-library/src/aws_library/s3/_client.py @@ -13,9 +13,13 @@ from boto3.s3.transfer import TransferConfig from botocore import exceptions as botocore_exc from botocore.client import Config +from models_library.api_schemas_storage.rest.storage_schemas import ( + ETag, + S3BucketName, + UploadedPart, +) from models_library.basic_types import SHA256Str from models_library.bytes_iters import BytesIter, DataSize -from models_library.storage_schemas import ETag, S3BucketName, UploadedPart from pydantic import AnyUrl, ByteSize, TypeAdapter from servicelib.bytes_iters import DEFAULT_READ_CHUNK_SIZE, BytesStreamer from servicelib.logging_utils import log_catch, log_context diff --git a/packages/aws-library/src/aws_library/s3/_models.py b/packages/aws-library/src/aws_library/s3/_models.py index 4e37d0dafc2..052859d3894 100644 --- a/packages/aws-library/src/aws_library/s3/_models.py +++ b/packages/aws-library/src/aws_library/s3/_models.py @@ -2,8 +2,8 @@ from dataclasses import dataclass from typing import TypeAlias +from models_library.api_schemas_storage.rest.storage_schemas import ETag from models_library.basic_types import SHA256Str -from models_library.storage_schemas import ETag from pydantic import AnyUrl, BaseModel, ByteSize from types_aiobotocore_s3.type_defs import HeadObjectOutputTypeDef, ObjectTypeDef diff --git a/packages/aws-library/tests/test_s3_client.py b/packages/aws-library/tests/test_s3_client.py index cc5957d2ab6..4f8b5bbe395 100644 --- a/packages/aws-library/tests/test_s3_client.py +++ b/packages/aws-library/tests/test_s3_client.py @@ -38,8 +38,11 @@ ) from aws_library.s3._models import MultiPartUploadLinks from faker import Faker +from models_library.api_schemas_storage.rest.storage_schemas import ( + S3BucketName, + UploadedPart, +) from models_library.basic_types import SHA256Str -from models_library.storage_schemas import S3BucketName, UploadedPart from moto.server import ThreadedMotoServer from pydantic import AnyUrl, ByteSize, TypeAdapter from pytest_benchmark.plugin import BenchmarkFixture diff --git a/packages/models-library/src/models_library/storage_schemas.py b/packages/models-library/src/models_library/api_schemas_storage/rest/storage_schemas.py similarity index 98% rename from packages/models-library/src/models_library/storage_schemas.py rename to packages/models-library/src/models_library/api_schemas_storage/rest/storage_schemas.py index aa56821bde2..f5388365474 100644 --- a/packages/models-library/src/models_library/storage_schemas.py +++ b/packages/models-library/src/models_library/api_schemas_storage/rest/storage_schemas.py @@ -28,10 +28,10 @@ ) from pydantic.networks import AnyUrl -from .basic_regex import DATCORE_DATASET_NAME_RE, S3_BUCKET_NAME_RE -from .basic_types import SHA256Str -from .generics import ListModel -from .projects_nodes_io import ( +from ...basic_regex import DATCORE_DATASET_NAME_RE, S3_BUCKET_NAME_RE +from ...basic_types import SHA256Str +from ...generics import ListModel +from ...projects_nodes_io import ( LocationID, LocationName, NodeID, diff --git a/packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py b/packages/models-library/src/models_library/api_schemas_storage/rpc/data_export_async_jobs.py similarity index 100% rename from packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py rename to packages/models-library/src/models_library/api_schemas_storage/rpc/data_export_async_jobs.py diff --git a/packages/models-library/src/models_library/api_schemas_webserver/storage.py b/packages/models-library/src/models_library/api_schemas_webserver/storage.py index 2e768afc9af..557332396a9 100644 --- a/packages/models-library/src/models_library/api_schemas_webserver/storage.py +++ b/packages/models-library/src/models_library/api_schemas_webserver/storage.py @@ -10,7 +10,7 @@ AsyncJobResult, AsyncJobStatus, ) -from ..api_schemas_storage.data_export_async_jobs import DataExportTaskStartInput +from ..api_schemas_storage.rpc.data_export_async_jobs import DataExportTaskStartInput from ..progress_bar import ProgressReport from ..projects_nodes_io import LocationID from ..users import UserID diff --git a/packages/pytest-simcore/src/pytest_simcore/helpers/s3.py b/packages/pytest-simcore/src/pytest_simcore/helpers/s3.py index 0a51779192b..52420362dd9 100644 --- a/packages/pytest-simcore/src/pytest_simcore/helpers/s3.py +++ b/packages/pytest-simcore/src/pytest_simcore/helpers/s3.py @@ -8,7 +8,11 @@ import orjson from aws_library.s3 import MultiPartUploadLinks from fastapi import status -from models_library.storage_schemas import ETag, FileUploadSchema, UploadedPart +from models_library.api_schemas_storage.rest.storage_schemas import ( + ETag, + FileUploadSchema, + UploadedPart, +) from pydantic import AnyUrl, ByteSize, TypeAdapter from servicelib.utils import limited_as_completed, logged_gather from types_aiobotocore_s3 import S3Client diff --git a/packages/pytest-simcore/src/pytest_simcore/services_api_mocks_for_aiohttp_clients.py b/packages/pytest-simcore/src/pytest_simcore/services_api_mocks_for_aiohttp_clients.py index c281df43595..303cf27c36d 100644 --- a/packages/pytest-simcore/src/pytest_simcore/services_api_mocks_for_aiohttp_clients.py +++ b/packages/pytest-simcore/src/pytest_simcore/services_api_mocks_for_aiohttp_clients.py @@ -12,10 +12,7 @@ from aioresponses.core import CallbackResult from faker import Faker from models_library.api_schemas_directorv2.comp_tasks import ComputationGet -from models_library.generics import Envelope -from models_library.projects_pipeline import ComputationTask -from models_library.projects_state import RunningState -from models_library.storage_schemas import ( +from models_library.api_schemas_storage.rest.storage_schemas import ( FileMetaDataGet, FileUploadCompleteFutureResponse, FileUploadCompleteResponse, @@ -25,6 +22,9 @@ LinkType, PresignedLink, ) +from models_library.generics import Envelope +from models_library.projects_pipeline import ComputationTask +from models_library.projects_state import RunningState from models_library.utils.fastapi_encoders import jsonable_encoder from pydantic import AnyUrl, ByteSize, TypeAdapter from servicelib.aiohttp import status diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py index 1b773fbdec9..503649042e3 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py @@ -2,7 +2,7 @@ from models_library.api_schemas_rpc_async_jobs.async_jobs import AsyncJobGet from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE -from models_library.api_schemas_storage.data_export_async_jobs import ( +from models_library.api_schemas_storage.rpc.data_export_async_jobs import ( DataExportTaskStartInput, ) from models_library.rabbitmq_basic_types import RPCMethodName diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports_common/_filemanager_utils.py b/packages/simcore-sdk/src/simcore_sdk/node_ports_common/_filemanager_utils.py index 9042568652f..208ff9ff344 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports_common/_filemanager_utils.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports_common/_filemanager_utils.py @@ -2,9 +2,7 @@ from typing import cast from aiohttp import ClientError, ClientSession -from models_library.generics import Envelope -from models_library.projects_nodes_io import LocationID, LocationName -from models_library.storage_schemas import ( +from models_library.api_schemas_storage.rest.storage_schemas import ( ETag, FileUploadCompleteFutureResponse, FileUploadCompleteResponse, @@ -12,6 +10,8 @@ FileUploadCompletionBody, UploadedPart, ) +from models_library.generics import Envelope +from models_library.projects_nodes_io import LocationID, LocationName from models_library.users import UserID from models_library.utils.fastapi_encoders import jsonable_encoder from pydantic import AnyUrl, TypeAdapter diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports_common/file_io_utils.py b/packages/simcore-sdk/src/simcore_sdk/node_ports_common/file_io_utils.py index 5e1d6461b65..3d9cab2c1c2 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports_common/file_io_utils.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports_common/file_io_utils.py @@ -17,8 +17,12 @@ ClientSession, RequestInfo, ) +from models_library.api_schemas_storage.rest.storage_schemas import ( + ETag, + FileUploadSchema, + UploadedPart, +) from models_library.basic_types import SHA256Str -from models_library.storage_schemas import ETag, FileUploadSchema, UploadedPart from multidict import MultiMapping from pydantic import AnyUrl, NonNegativeInt from servicelib.aiohttp import status diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports_common/filemanager.py b/packages/simcore-sdk/src/simcore_sdk/node_ports_common/filemanager.py index ca0a54875ea..b29bc8e7bea 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports_common/filemanager.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports_common/filemanager.py @@ -5,15 +5,15 @@ import aiofiles from aiohttp import ClientSession -from models_library.basic_types import SHA256Str -from models_library.projects_nodes_io import LocationID, LocationName, StorageFileID -from models_library.storage_schemas import ( +from models_library.api_schemas_storage.rest.storage_schemas import ( ETag, FileMetaDataGet, FileUploadSchema, LinkType, UploadedPart, ) +from models_library.basic_types import SHA256Str +from models_library.projects_nodes_io import LocationID, LocationName, StorageFileID from models_library.users import UserID from pydantic import AnyUrl, ByteSize, TypeAdapter from servicelib.file_utils import create_sha256_checksum diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports_common/storage_client.py b/packages/simcore-sdk/src/simcore_sdk/node_ports_common/storage_client.py index a97905b7ec1..f8a7cb2b378 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports_common/storage_client.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports_common/storage_client.py @@ -10,16 +10,16 @@ from aiohttp import ClientResponse, ClientSession from aiohttp import client as aiohttp_client_module from aiohttp.client_exceptions import ClientConnectionError, ClientResponseError -from models_library.basic_types import SHA256Str -from models_library.generics import Envelope -from models_library.projects_nodes_io import LocationID, StorageFileID -from models_library.storage_schemas import ( +from models_library.api_schemas_storage.rest.storage_schemas import ( FileLocationArray, FileMetaDataGet, FileUploadSchema, LinkType, PresignedLink, ) +from models_library.basic_types import SHA256Str +from models_library.generics import Envelope +from models_library.projects_nodes_io import LocationID, StorageFileID from models_library.users import UserID from pydantic import ByteSize from pydantic.networks import AnyUrl diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/__init__.py b/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/__init__.py index 5c107efdf62..b62b45235c0 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/__init__.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/__init__.py @@ -1,8 +1,10 @@ import logging +from models_library.api_schemas_storage.rest.storage_schemas import ( + LinkType as FileLinkType, +) from models_library.projects import ProjectIDStr from models_library.projects_nodes_io import NodeIDStr -from models_library.storage_schemas import LinkType as FileLinkType from models_library.users import UserID from settings_library.aws_s3_cli import AwsS3CliSettings from settings_library.r_clone import RCloneSettings diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/nodeports_v2.py b/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/nodeports_v2.py index fa7489ac36f..b5e23bce0c5 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/nodeports_v2.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/nodeports_v2.py @@ -6,10 +6,10 @@ from pathlib import Path from typing import Any +from models_library.api_schemas_storage.rest.storage_schemas import LinkType from models_library.projects import ProjectIDStr from models_library.projects_nodes_io import NodeIDStr from models_library.services_types import ServicePortKey -from models_library.storage_schemas import LinkType from models_library.users import UserID from pydantic import BaseModel, ConfigDict, Field, ValidationError from pydantic_core import InitErrorDetails diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/port.py b/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/port.py index ef21f8c653a..ee88602e4db 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/port.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/port.py @@ -6,9 +6,9 @@ from pprint import pformat from typing import Any +from models_library.api_schemas_storage.rest.storage_schemas import LinkType from models_library.services_io import BaseServiceIOModel from models_library.services_types import ServicePortKey -from models_library.storage_schemas import LinkType from pydantic import ( AnyUrl, ConfigDict, diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/port_utils.py b/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/port_utils.py index 20fd414bc05..93705297f95 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/port_utils.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/port_utils.py @@ -4,9 +4,12 @@ from pathlib import Path from typing import Any +from models_library.api_schemas_storage.rest.storage_schemas import ( + FileUploadSchema, + LinkType, +) from models_library.basic_types import SHA256Str from models_library.services_types import FileName, ServicePortKey -from models_library.storage_schemas import FileUploadSchema, LinkType from models_library.users import UserID from pydantic import AnyUrl, ByteSize, TypeAdapter from servicelib.progress_bar import ProgressBarData diff --git a/packages/simcore-sdk/tests/integration/conftest.py b/packages/simcore-sdk/tests/integration/conftest.py index f8dc1e5029c..e9195dcf986 100644 --- a/packages/simcore-sdk/tests/integration/conftest.py +++ b/packages/simcore-sdk/tests/integration/conftest.py @@ -13,9 +13,9 @@ import pytest import sqlalchemy as sa from aiohttp import ClientSession +from models_library.api_schemas_storage.rest.storage_schemas import FileUploadSchema from models_library.generics import Envelope from models_library.projects_nodes_io import LocationID, NodeIDStr, SimcoreS3FileID -from models_library.storage_schemas import FileUploadSchema from models_library.users import UserID from pydantic import TypeAdapter from pytest_simcore.helpers.faker_factories import random_project, random_user diff --git a/packages/simcore-sdk/tests/unit/test_node_ports_common_file_io_utils.py b/packages/simcore-sdk/tests/unit/test_node_ports_common_file_io_utils.py index f353494e01d..49772ed2a4c 100644 --- a/packages/simcore-sdk/tests/unit/test_node_ports_common_file_io_utils.py +++ b/packages/simcore-sdk/tests/unit/test_node_ports_common_file_io_utils.py @@ -13,7 +13,7 @@ from aiohttp import ClientResponse, ClientSession, TCPConnector from aioresponses import aioresponses from faker import Faker -from models_library.storage_schemas import ( +from models_library.api_schemas_storage.rest.storage_schemas import ( FileUploadLinks, FileUploadSchema, UploadedPart, diff --git a/packages/simcore-sdk/tests/unit/test_node_ports_v2_port.py b/packages/simcore-sdk/tests/unit/test_node_ports_v2_port.py index 50476a7b15f..c507bb9c0e6 100644 --- a/packages/simcore-sdk/tests/unit/test_node_ports_v2_port.py +++ b/packages/simcore-sdk/tests/unit/test_node_ports_v2_port.py @@ -21,8 +21,8 @@ from aiohttp.client import ClientSession from aioresponses import aioresponses as AioResponsesMock from faker import Faker +from models_library.api_schemas_storage.rest.storage_schemas import FileMetaDataGet from models_library.projects_nodes_io import LocationID -from models_library.storage_schemas import FileMetaDataGet from pydantic import TypeAdapter, ValidationError from pytest_mock.plugin import MockerFixture from servicelib.progress_bar import ProgressBarData diff --git a/packages/simcore-sdk/tests/unit/test_storage_client.py b/packages/simcore-sdk/tests/unit/test_storage_client.py index c094b7a04b9..c91f356756e 100644 --- a/packages/simcore-sdk/tests/unit/test_storage_client.py +++ b/packages/simcore-sdk/tests/unit/test_storage_client.py @@ -12,13 +12,13 @@ import pytest from aioresponses import aioresponses as AioResponsesMock from faker import Faker -from models_library.projects_nodes_io import SimcoreS3FileID -from models_library.storage_schemas import ( +from models_library.api_schemas_storage.rest.storage_schemas import ( FileLocationArray, FileMetaDataGet, FileUploadSchema, LocationID, ) +from models_library.projects_nodes_io import SimcoreS3FileID from models_library.users import UserID from pydantic import AnyUrl, ByteSize, TypeAdapter from pytest_simcore.helpers.monkeypatch_envs import EnvVarsDict, setenvs_from_dict diff --git a/services/api-server/src/simcore_service_api_server/api/routes/files.py b/services/api-server/src/simcore_service_api_server/api/routes/files.py index 4b15c92e5c2..40031eff706 100644 --- a/services/api-server/src/simcore_service_api_server/api/routes/files.py +++ b/services/api-server/src/simcore_service_api_server/api/routes/files.py @@ -10,8 +10,12 @@ from fastapi import Header, Request, UploadFile, status from fastapi.exceptions import HTTPException from fastapi_pagination.api import create_page +from models_library.api_schemas_storage.rest.storage_schemas import ( + ETag, + FileUploadCompletionBody, + LinkType, +) from models_library.basic_types import SHA256Str -from models_library.storage_schemas import ETag, FileUploadCompletionBody, LinkType from pydantic import AnyUrl, ByteSize, PositiveInt, TypeAdapter, ValidationError from servicelib.fastapi.requests_decorators import cancel_on_disconnect from simcore_sdk.node_ports_common.constants import SIMCORE_LOCATION diff --git a/services/api-server/src/simcore_service_api_server/models/schemas/files.py b/services/api-server/src/simcore_service_api_server/models/schemas/files.py index 4aae188c1a0..2443ab0d99d 100644 --- a/services/api-server/src/simcore_service_api_server/models/schemas/files.py +++ b/services/api-server/src/simcore_service_api_server/models/schemas/files.py @@ -7,9 +7,9 @@ import aiofiles from fastapi import UploadFile +from models_library.api_schemas_storage.rest.storage_schemas import ETag from models_library.basic_types import SHA256Str from models_library.projects_nodes_io import StorageFileID -from models_library.storage_schemas import ETag from pydantic import ( AnyHttpUrl, BaseModel, diff --git a/services/api-server/src/simcore_service_api_server/services_http/storage.py b/services/api-server/src/simcore_service_api_server/services_http/storage.py index 4abcef973f4..087e93e3a23 100644 --- a/services/api-server/src/simcore_service_api_server/services_http/storage.py +++ b/services/api-server/src/simcore_service_api_server/services_http/storage.py @@ -8,11 +8,16 @@ from fastapi import FastAPI from fastapi.encoders import jsonable_encoder +from models_library.api_schemas_storage.rest.storage_schemas import FileMetaDataArray +from models_library.api_schemas_storage.rest.storage_schemas import ( + FileMetaDataGet as StorageFileMetaData, +) +from models_library.api_schemas_storage.rest.storage_schemas import ( + FileUploadSchema, + PresignedLink, +) from models_library.basic_types import SHA256Str from models_library.generics import Envelope -from models_library.storage_schemas import FileMetaDataArray -from models_library.storage_schemas import FileMetaDataGet as StorageFileMetaData -from models_library.storage_schemas import FileUploadSchema, PresignedLink from pydantic import AnyUrl, PositiveInt from settings_library.tracing import TracingSettings from starlette.datastructures import URL diff --git a/services/api-server/tests/unit/conftest.py b/services/api-server/tests/unit/conftest.py index a36a4f7513a..3cf005f6ede 100644 --- a/services/api-server/tests/unit/conftest.py +++ b/services/api-server/tests/unit/conftest.py @@ -27,12 +27,12 @@ TaskProgress, TaskStatus, ) +from models_library.api_schemas_storage.rest.storage_schemas import HealthCheck from models_library.api_schemas_webserver.projects import ProjectGet from models_library.app_diagnostics import AppStatusCheck from models_library.generics import Envelope from models_library.projects import ProjectID from models_library.projects_nodes_io import BaseFileLink, SimcoreS3FileID -from models_library.storage_schemas import HealthCheck from models_library.users import UserID from moto.server import ThreadedMotoServer from packaging.version import Version diff --git a/services/api-server/tests/unit/test_api_files.py b/services/api-server/tests/unit/test_api_files.py index ed8666e693c..a876f0a5d11 100644 --- a/services/api-server/tests/unit/test_api_files.py +++ b/services/api-server/tests/unit/test_api_files.py @@ -18,8 +18,12 @@ from fastapi import status from fastapi.encoders import jsonable_encoder from httpx import AsyncClient +from models_library.api_schemas_storage.rest.storage_schemas import ( + ETag, + FileUploadCompletionBody, + UploadedPart, +) from models_library.basic_types import SHA256Str -from models_library.storage_schemas import ETag, FileUploadCompletionBody, UploadedPart from pydantic import TypeAdapter from pytest_simcore.helpers.httpx_calls_capture_models import ( CreateRespxMockCallback, diff --git a/services/api-server/tests/unit/test_models_schemas_files.py b/services/api-server/tests/unit/test_models_schemas_files.py index 1ced425d5f9..99eab62cdac 100644 --- a/services/api-server/tests/unit/test_models_schemas_files.py +++ b/services/api-server/tests/unit/test_models_schemas_files.py @@ -11,9 +11,11 @@ import pytest from fastapi import UploadFile +from models_library.api_schemas_storage.rest.storage_schemas import ( + FileMetaDataGet as StorageFileMetaData, +) from models_library.basic_types import SHA256Str from models_library.projects_nodes_io import StorageFileID -from models_library.storage_schemas import FileMetaDataGet as StorageFileMetaData from pydantic import TypeAdapter, ValidationError from simcore_service_api_server.models.schemas.files import File from simcore_service_api_server.services_http.storage import to_file_api_model diff --git a/services/director-v2/tests/unit/with_dbs/test_utils_dask.py b/services/director-v2/tests/unit/with_dbs/test_utils_dask.py index 147b857213d..70c63c35a1f 100644 --- a/services/director-v2/tests/unit/with_dbs/test_utils_dask.py +++ b/services/director-v2/tests/unit/with_dbs/test_utils_dask.py @@ -30,11 +30,14 @@ from faker import Faker from fastapi import FastAPI from models_library.api_schemas_directorv2.services import NodeRequirements +from models_library.api_schemas_storage.rest.storage_schemas import ( + FileUploadLinks, + FileUploadSchema, +) from models_library.docker import to_simcore_runtime_docker_label_key from models_library.projects import ProjectID from models_library.projects_nodes_io import NodeID, SimCoreFileLink, SimcoreS3FileID from models_library.services import ServiceRunID -from models_library.storage_schemas import FileUploadLinks, FileUploadSchema from models_library.users import UserID from pydantic import ByteSize, TypeAdapter from pydantic.networks import AnyUrl diff --git a/services/dynamic-sidecar/tests/integration/test_modules_long_running_tasks.py b/services/dynamic-sidecar/tests/integration/test_modules_long_running_tasks.py index 7302e23f209..444c7675f00 100644 --- a/services/dynamic-sidecar/tests/integration/test_modules_long_running_tasks.py +++ b/services/dynamic-sidecar/tests/integration/test_modules_long_running_tasks.py @@ -19,9 +19,9 @@ from botocore.client import Config from botocore.exceptions import ClientError from fastapi import FastAPI +from models_library.api_schemas_storage.rest.storage_schemas import S3BucketName from models_library.projects import ProjectID from models_library.projects_nodes_io import NodeID, SimcoreS3FileID -from models_library.storage_schemas import S3BucketName from models_library.users import UserID from pydantic import TypeAdapter from pytest_mock import MockerFixture diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/service_runs_db.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/service_runs_db.py index 62feebd0e0c..a4a2df0a07f 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/service_runs_db.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/service_runs_db.py @@ -9,6 +9,7 @@ from models_library.api_schemas_resource_usage_tracker.credit_transactions import ( WalletTotalCredits, ) +from models_library.api_schemas_storage.rest.storage_schemas import S3BucketName from models_library.products import ProductName from models_library.projects import ProjectID from models_library.resource_tracker import ( @@ -18,7 +19,6 @@ ) from models_library.rest_ordering import OrderBy, OrderDirection from models_library.services_types import ServiceRunID -from models_library.storage_schemas import S3BucketName from models_library.users import UserID from models_library.wallets import WalletID from pydantic import PositiveInt diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/s3.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/s3.py index 1cdd7d07673..aec2aa449ca 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/s3.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/s3.py @@ -3,7 +3,7 @@ from aws_library.s3 import S3NotConnectedError, SimcoreS3API from fastapi import FastAPI -from models_library.storage_schemas import S3BucketName +from models_library.api_schemas_storage.rest.storage_schemas import S3BucketName from pydantic import TypeAdapter from servicelib.logging_utils import log_context from settings_library.s3 import S3Settings diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/service_runs.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/service_runs.py index 6d1718e8531..0591861dba7 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/service_runs.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/service_runs.py @@ -12,6 +12,7 @@ ServiceRunGet, ServiceRunPage, ) +from models_library.api_schemas_storage.rest.storage_schemas import S3BucketName from models_library.products import ProductName from models_library.projects import ProjectID from models_library.resource_tracker import ( @@ -21,7 +22,6 @@ ServicesAggregatedUsagesType, ) from models_library.rest_ordering import OrderBy -from models_library.storage_schemas import S3BucketName from models_library.users import UserID from models_library.wallets import WalletID from pydantic import AnyUrl, TypeAdapter diff --git a/services/storage/src/simcore_service_storage/api/rest/_datasets.py b/services/storage/src/simcore_service_storage/api/rest/_datasets.py index a71e4a3fa46..200fc6dc0fa 100644 --- a/services/storage/src/simcore_service_storage/api/rest/_datasets.py +++ b/services/storage/src/simcore_service_storage/api/rest/_datasets.py @@ -2,9 +2,12 @@ from typing import Annotated from fastapi import APIRouter, Depends, Request +from models_library.api_schemas_storage.rest.storage_schemas import ( + DatasetMetaDataGet, + FileMetaDataGet, +) from models_library.generics import Envelope from models_library.projects_nodes_io import LocationID -from models_library.storage_schemas import DatasetMetaDataGet, FileMetaDataGet from ...dsm import get_dsm_provider from ...models import FilesMetadataDatasetQueryParams, StorageQueryParamsBase diff --git a/services/storage/src/simcore_service_storage/api/rest/_files.py b/services/storage/src/simcore_service_storage/api/rest/_files.py index 2e1f3dcea7a..8c71a2395ed 100644 --- a/services/storage/src/simcore_service_storage/api/rest/_files.py +++ b/services/storage/src/simcore_service_storage/api/rest/_files.py @@ -3,9 +3,7 @@ from typing import Annotated, cast from fastapi import APIRouter, Depends, Header, HTTPException, Request -from models_library.generics import Envelope -from models_library.projects_nodes_io import LocationID, StorageFileID -from models_library.storage_schemas import ( +from models_library.api_schemas_storage.rest.storage_schemas import ( FileMetaDataGet, FileMetaDataGetv010, FileUploadCompleteFutureResponse, @@ -17,6 +15,8 @@ FileUploadSchema, SoftCopyBody, ) +from models_library.generics import Envelope +from models_library.projects_nodes_io import LocationID, StorageFileID from pydantic import AnyUrl, ByteSize, TypeAdapter from servicelib.aiohttp import status from yarl import URL diff --git a/services/storage/src/simcore_service_storage/api/rest/_health.py b/services/storage/src/simcore_service_storage/api/rest/_health.py index 2535478f735..16f69425db7 100644 --- a/services/storage/src/simcore_service_storage/api/rest/_health.py +++ b/services/storage/src/simcore_service_storage/api/rest/_health.py @@ -8,9 +8,12 @@ from aws_library.s3 import S3AccessError from fastapi import APIRouter, Request +from models_library.api_schemas_storage.rest.storage_schemas import ( + HealthCheck, + S3BucketName, +) from models_library.app_diagnostics import AppStatusCheck from models_library.generics import Envelope -from models_library.storage_schemas import HealthCheck, S3BucketName from pydantic import TypeAdapter from servicelib.db_asyncpg_utils import check_postgres_liveness from servicelib.fastapi.db_asyncpg_engine import get_engine diff --git a/services/storage/src/simcore_service_storage/api/rest/_locations.py b/services/storage/src/simcore_service_storage/api/rest/_locations.py index 2110e322339..253aaf1028b 100644 --- a/services/storage/src/simcore_service_storage/api/rest/_locations.py +++ b/services/storage/src/simcore_service_storage/api/rest/_locations.py @@ -2,8 +2,8 @@ from typing import Annotated from fastapi import APIRouter, Depends, Request, status +from models_library.api_schemas_storage.rest.storage_schemas import FileLocation from models_library.generics import Envelope -from models_library.storage_schemas import FileLocation # Exclusive for simcore-s3 storage ----------------------- from ...dsm import get_dsm_provider diff --git a/services/storage/src/simcore_service_storage/api/rest/_simcore_s3.py b/services/storage/src/simcore_service_storage/api/rest/_simcore_s3.py index 650a8c3c933..28e489381a1 100644 --- a/services/storage/src/simcore_service_storage/api/rest/_simcore_s3.py +++ b/services/storage/src/simcore_service_storage/api/rest/_simcore_s3.py @@ -5,9 +5,12 @@ from fastapi import APIRouter, Depends, FastAPI, Request from models_library.api_schemas_long_running_tasks.base import TaskProgress from models_library.api_schemas_long_running_tasks.tasks import TaskGet +from models_library.api_schemas_storage.rest.storage_schemas import ( + FileMetaDataGet, + FoldersBody, +) from models_library.generics import Envelope from models_library.projects import ProjectID -from models_library.storage_schemas import FileMetaDataGet, FoldersBody from servicelib.aiohttp import status from servicelib.fastapi.long_running_tasks._dependencies import get_tasks_manager from servicelib.logging_utils import log_context diff --git a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py index 8dc6a033d88..2dc5b7fd6cd 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py @@ -2,7 +2,7 @@ from fastapi import FastAPI from models_library.api_schemas_rpc_async_jobs.async_jobs import AsyncJobGet -from models_library.api_schemas_storage.data_export_async_jobs import ( +from models_library.api_schemas_storage.rpc.data_export_async_jobs import ( AccessRightError, DataExportError, DataExportTaskStartInput, diff --git a/services/storage/src/simcore_service_storage/constants.py b/services/storage/src/simcore_service_storage/constants.py index ea3aa84d967..d613c789be2 100644 --- a/services/storage/src/simcore_service_storage/constants.py +++ b/services/storage/src/simcore_service_storage/constants.py @@ -1,7 +1,7 @@ from typing import Final from aws_library.s3 import PRESIGNED_LINK_MAX_SIZE, S3_MAX_FILE_SIZE -from models_library.storage_schemas import LinkType +from models_library.api_schemas_storage.rest.storage_schemas import LinkType from pydantic import ByteSize RETRY_WAIT_SECS = 2 diff --git a/services/storage/src/simcore_service_storage/datcore_dsm.py b/services/storage/src/simcore_service_storage/datcore_dsm.py index 97c4408b5b5..629d4e69dee 100644 --- a/services/storage/src/simcore_service_storage/datcore_dsm.py +++ b/services/storage/src/simcore_service_storage/datcore_dsm.py @@ -1,10 +1,14 @@ from dataclasses import dataclass from fastapi import FastAPI +from models_library.api_schemas_storage.rest.storage_schemas import ( + DatCoreDatasetName, + LinkType, + UploadedPart, +) from models_library.basic_types import SHA256Str from models_library.projects import ProjectID from models_library.projects_nodes_io import LocationID, LocationName, StorageFileID -from models_library.storage_schemas import DatCoreDatasetName, LinkType, UploadedPart from models_library.users import UserID from pydantic import AnyUrl, ByteSize diff --git a/services/storage/src/simcore_service_storage/dsm_factory.py b/services/storage/src/simcore_service_storage/dsm_factory.py index b1576b6e020..5796e994165 100644 --- a/services/storage/src/simcore_service_storage/dsm_factory.py +++ b/services/storage/src/simcore_service_storage/dsm_factory.py @@ -3,10 +3,13 @@ from dataclasses import dataclass, field from fastapi import FastAPI +from models_library.api_schemas_storage.rest.storage_schemas import ( + LinkType, + UploadedPart, +) from models_library.basic_types import SHA256Str from models_library.projects import ProjectID from models_library.projects_nodes_io import LocationID, LocationName, StorageFileID -from models_library.storage_schemas import LinkType, UploadedPart from models_library.users import UserID from pydantic import AnyUrl, ByteSize diff --git a/services/storage/src/simcore_service_storage/models.py b/services/storage/src/simcore_service_storage/models.py index c90154bb002..207da2936e7 100644 --- a/services/storage/src/simcore_service_storage/models.py +++ b/services/storage/src/simcore_service_storage/models.py @@ -6,6 +6,15 @@ import arrow from aws_library.s3 import UploadID +from models_library.api_schemas_storage.rest.storage_schemas import ( + UNDEFINED_SIZE, + UNDEFINED_SIZE_TYPE, + DatasetMetaDataGet, + ETag, + FileMetaDataGet, + LinkType, + S3BucketName, +) from models_library.basic_types import SHA256Str from models_library.projects import ProjectID from models_library.projects_nodes_io import ( @@ -19,15 +28,6 @@ DEFAULT_NUMBER_OF_ITEMS_PER_PAGE, MAXIMUM_NUMBER_OF_ITEMS_PER_PAGE, ) -from models_library.storage_schemas import ( - UNDEFINED_SIZE, - UNDEFINED_SIZE_TYPE, - DatasetMetaDataGet, - ETag, - FileMetaDataGet, - LinkType, - S3BucketName, -) from models_library.users import UserID from models_library.utils.common_validators import empty_str_to_none_pre_validator from pydantic import ( diff --git a/services/storage/src/simcore_service_storage/modules/datcore_adapter/datcore_adapter.py b/services/storage/src/simcore_service_storage/modules/datcore_adapter/datcore_adapter.py index 1ed8077550e..8fa1330e013 100644 --- a/services/storage/src/simcore_service_storage/modules/datcore_adapter/datcore_adapter.py +++ b/services/storage/src/simcore_service_storage/modules/datcore_adapter/datcore_adapter.py @@ -5,7 +5,7 @@ import httpx from fastapi import FastAPI -from models_library.storage_schemas import DatCoreDatasetName +from models_library.api_schemas_storage.rest.storage_schemas import DatCoreDatasetName from models_library.users import UserID from pydantic import AnyUrl, TypeAdapter from servicelib.fastapi.client_session import get_client_session diff --git a/services/storage/src/simcore_service_storage/simcore_s3_dsm.py b/services/storage/src/simcore_service_storage/simcore_s3_dsm.py index 7911a010be8..dfbb66a9a6d 100644 --- a/services/storage/src/simcore_service_storage/simcore_s3_dsm.py +++ b/services/storage/src/simcore_service_storage/simcore_s3_dsm.py @@ -18,6 +18,13 @@ UploadID, ) from fastapi import FastAPI +from models_library.api_schemas_storage.rest.storage_schemas import ( + UNDEFINED_SIZE, + UNDEFINED_SIZE_TYPE, + LinkType, + S3BucketName, + UploadedPart, +) from models_library.basic_types import SHA256Str from models_library.projects import ProjectID from models_library.projects_nodes_io import ( @@ -26,13 +33,6 @@ SimcoreS3FileID, StorageFileID, ) -from models_library.storage_schemas import ( - UNDEFINED_SIZE, - UNDEFINED_SIZE_TYPE, - LinkType, - S3BucketName, - UploadedPart, -) from models_library.users import UserID from pydantic import AnyUrl, ByteSize, NonNegativeInt, TypeAdapter from servicelib.aiohttp.long_running_tasks.server import TaskProgress diff --git a/services/storage/src/simcore_service_storage/utils/simcore_s3_dsm_utils.py b/services/storage/src/simcore_service_storage/utils/simcore_s3_dsm_utils.py index 921facf24d1..aaebe3a7e4d 100644 --- a/services/storage/src/simcore_service_storage/utils/simcore_s3_dsm_utils.py +++ b/services/storage/src/simcore_service_storage/utils/simcore_s3_dsm_utils.py @@ -2,12 +2,12 @@ from pathlib import Path from aws_library.s3 import S3MetaData, SimcoreS3API +from models_library.api_schemas_storage.rest.storage_schemas import S3BucketName from models_library.projects_nodes_io import ( SimcoreS3DirectoryID, SimcoreS3FileID, StorageFileID, ) -from models_library.storage_schemas import S3BucketName from pydantic import ByteSize, NonNegativeInt, TypeAdapter from servicelib.utils import ensure_ends_with from sqlalchemy.ext.asyncio import AsyncConnection diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index fefbaa157ee..46ccf685a89 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -23,11 +23,7 @@ from faker import Faker from fakeredis.aioredis import FakeRedis from fastapi import FastAPI -from models_library.basic_types import SHA256Str -from models_library.projects import ProjectID -from models_library.projects_nodes import NodeID -from models_library.projects_nodes_io import LocationID, SimcoreS3FileID -from models_library.storage_schemas import ( +from models_library.api_schemas_storage.rest.storage_schemas import ( FileMetaDataGet, FileUploadCompleteFutureResponse, FileUploadCompleteResponse, @@ -37,6 +33,10 @@ LinkType, UploadedPart, ) +from models_library.basic_types import SHA256Str +from models_library.projects import ProjectID +from models_library.projects_nodes import NodeID +from models_library.projects_nodes_io import LocationID, SimcoreS3FileID from models_library.users import UserID from models_library.utils.fastapi_encoders import jsonable_encoder from pydantic import ByteSize, TypeAdapter diff --git a/services/storage/tests/unit/test_data_export.py b/services/storage/tests/unit/test_data_export.py index a7912a60c30..e5ccdc26fbd 100644 --- a/services/storage/tests/unit/test_data_export.py +++ b/services/storage/tests/unit/test_data_export.py @@ -14,7 +14,7 @@ AsyncJobStatus, ) from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE -from models_library.api_schemas_storage.data_export_async_jobs import ( +from models_library.api_schemas_storage.rpc.data_export_async_jobs import ( DataExportTaskStartInput, ) from pytest_mock import MockerFixture @@ -78,7 +78,9 @@ async def rpc_client( async def test_start_data_export(rpc_client: RabbitMQRPCClient, faker: Faker): result = await data_export.start_data_export( rpc_client, - paths=DataExportTaskStartInput(location_id=0, paths=[Path(faker.file_path())]), + paths=DataExportTaskStartInput( + user_id=1, location_id=0, paths=[Path(faker.file_path())] + ), ) assert isinstance(result, AsyncJobGet) diff --git a/services/storage/tests/unit/test_dsm_dsmcleaner.py b/services/storage/tests/unit/test_dsm_dsmcleaner.py index 0d98e7d65de..9e354075e77 100644 --- a/services/storage/tests/unit/test_dsm_dsmcleaner.py +++ b/services/storage/tests/unit/test_dsm_dsmcleaner.py @@ -16,9 +16,9 @@ import pytest from aws_library.s3 import MultiPartUploadLinks, SimcoreS3API from faker import Faker +from models_library.api_schemas_storage.rest.storage_schemas import LinkType from models_library.basic_types import SHA256Str from models_library.projects_nodes_io import SimcoreS3DirectoryID, SimcoreS3FileID -from models_library.storage_schemas import LinkType from models_library.users import UserID from pydantic import ByteSize, TypeAdapter from pytest_simcore.helpers.parametrizations import byte_size_ids diff --git a/services/storage/tests/unit/test_dsm_soft_links.py b/services/storage/tests/unit/test_dsm_soft_links.py index 859d5c6be09..b67bc907dbf 100644 --- a/services/storage/tests/unit/test_dsm_soft_links.py +++ b/services/storage/tests/unit/test_dsm_soft_links.py @@ -8,8 +8,8 @@ import pytest from faker import Faker +from models_library.api_schemas_storage.rest.storage_schemas import S3BucketName from models_library.projects_nodes_io import SimcoreS3FileID -from models_library.storage_schemas import S3BucketName from models_library.users import UserID from models_library.utils.fastapi_encoders import jsonable_encoder from pydantic import ByteSize, TypeAdapter diff --git a/services/storage/tests/unit/test_handlers_datasets.py b/services/storage/tests/unit/test_handlers_datasets.py index 6f69b94d289..aa84bbe6e09 100644 --- a/services/storage/tests/unit/test_handlers_datasets.py +++ b/services/storage/tests/unit/test_handlers_datasets.py @@ -12,9 +12,12 @@ from faker import Faker from fastapi import FastAPI from httpx import AsyncClient +from models_library.api_schemas_storage.rest.storage_schemas import ( + DatasetMetaDataGet, + FileMetaDataGet, +) from models_library.projects import ProjectID from models_library.projects_nodes_io import SimcoreS3FileID -from models_library.storage_schemas import DatasetMetaDataGet, FileMetaDataGet from models_library.users import UserID from pydantic import ByteSize from pytest_mock import MockerFixture diff --git a/services/storage/tests/unit/test_handlers_files.py b/services/storage/tests/unit/test_handlers_files.py index 550d1e6e62b..af4ac601e0b 100644 --- a/services/storage/tests/unit/test_handlers_files.py +++ b/services/storage/tests/unit/test_handlers_files.py @@ -26,10 +26,7 @@ from aws_library.s3._constants import MULTIPART_UPLOADS_MIN_TOTAL_SIZE from faker import Faker from fastapi import FastAPI -from models_library.basic_types import SHA256Str -from models_library.projects import ProjectID -from models_library.projects_nodes_io import LocationID, NodeID, SimcoreS3FileID -from models_library.storage_schemas import ( +from models_library.api_schemas_storage.rest.storage_schemas import ( FileMetaDataGet, FileUploadCompleteFutureResponse, FileUploadCompleteResponse, @@ -41,6 +38,9 @@ SoftCopyBody, UploadedPart, ) +from models_library.basic_types import SHA256Str +from models_library.projects import ProjectID +from models_library.projects_nodes_io import LocationID, NodeID, SimcoreS3FileID from models_library.users import UserID from models_library.utils.fastapi_encoders import jsonable_encoder from pydantic import AnyUrl, ByteSize, TypeAdapter diff --git a/services/storage/tests/unit/test_handlers_files_metadata.py b/services/storage/tests/unit/test_handlers_files_metadata.py index 564bd500810..0881da8618e 100644 --- a/services/storage/tests/unit/test_handlers_files_metadata.py +++ b/services/storage/tests/unit/test_handlers_files_metadata.py @@ -13,8 +13,11 @@ import pytest from faker import Faker from fastapi import FastAPI +from models_library.api_schemas_storage.rest.storage_schemas import ( + FileMetaDataGet, + SimcoreS3FileID, +) from models_library.projects import ProjectID -from models_library.storage_schemas import FileMetaDataGet, SimcoreS3FileID from models_library.users import UserID from pydantic import ByteSize, TypeAdapter from pytest_simcore.helpers.fastapi import url_from_operation_id diff --git a/services/storage/tests/unit/test_handlers_health.py b/services/storage/tests/unit/test_handlers_health.py index 5556e6681ed..3f1958ab57f 100644 --- a/services/storage/tests/unit/test_handlers_health.py +++ b/services/storage/tests/unit/test_handlers_health.py @@ -7,8 +7,11 @@ import httpx import simcore_service_storage._meta from fastapi import FastAPI +from models_library.api_schemas_storage.rest.storage_schemas import ( + HealthCheck, + S3BucketName, +) from models_library.app_diagnostics import AppStatusCheck -from models_library.storage_schemas import HealthCheck, S3BucketName from moto.server import ThreadedMotoServer from pytest_simcore.helpers.fastapi import url_from_operation_id from pytest_simcore.helpers.httpx_assert_checks import assert_status diff --git a/services/storage/tests/unit/test_handlers_simcore_s3.py b/services/storage/tests/unit/test_handlers_simcore_s3.py index f5ff87df5d0..f6cbbf4b289 100644 --- a/services/storage/tests/unit/test_handlers_simcore_s3.py +++ b/services/storage/tests/unit/test_handlers_simcore_s3.py @@ -19,10 +19,13 @@ from aws_library.s3 import SimcoreS3API from faker import Faker from fastapi import FastAPI +from models_library.api_schemas_storage.rest.storage_schemas import ( + FileMetaDataGet, + FoldersBody, +) from models_library.basic_types import SHA256Str from models_library.projects import ProjectID from models_library.projects_nodes_io import NodeID, NodeIDStr, SimcoreS3FileID -from models_library.storage_schemas import FileMetaDataGet, FoldersBody from models_library.users import UserID from models_library.utils.fastapi_encoders import jsonable_encoder from pydantic import ByteSize, TypeAdapter diff --git a/services/storage/tests/unit/test_models.py b/services/storage/tests/unit/test_models.py index c64959ec42d..23f238d95a9 100644 --- a/services/storage/tests/unit/test_models.py +++ b/services/storage/tests/unit/test_models.py @@ -1,9 +1,9 @@ import uuid import pytest +from models_library.api_schemas_storage.rest.storage_schemas import S3BucketName from models_library.projects import ProjectID from models_library.projects_nodes_io import NodeID, SimcoreS3FileID, StorageFileID -from models_library.storage_schemas import S3BucketName from pydantic import TypeAdapter, ValidationError from simcore_service_storage.models import FileMetaData from simcore_service_storage.simcore_s3_dsm import SimcoreS3DataManager diff --git a/services/storage/tests/unit/test_simcore_s3_dsm.py b/services/storage/tests/unit/test_simcore_s3_dsm.py index 75707058dd5..68e40d0e90a 100644 --- a/services/storage/tests/unit/test_simcore_s3_dsm.py +++ b/services/storage/tests/unit/test_simcore_s3_dsm.py @@ -7,9 +7,9 @@ import pytest from faker import Faker +from models_library.api_schemas_storage.rest.storage_schemas import FileUploadSchema from models_library.basic_types import SHA256Str from models_library.projects_nodes_io import SimcoreS3FileID -from models_library.storage_schemas import FileUploadSchema from models_library.users import UserID from pydantic import ByteSize, TypeAdapter from simcore_service_storage.models import FileMetaData diff --git a/services/storage/tests/unit/test_utils.py b/services/storage/tests/unit/test_utils.py index b0c5e336170..86e7076271e 100644 --- a/services/storage/tests/unit/test_utils.py +++ b/services/storage/tests/unit/test_utils.py @@ -13,9 +13,9 @@ import httpx import pytest from faker import Faker +from models_library.api_schemas_storage.rest.storage_schemas import UNDEFINED_SIZE_TYPE from models_library.projects import ProjectID from models_library.projects_nodes_io import NodeID, SimcoreS3FileID -from models_library.storage_schemas import UNDEFINED_SIZE_TYPE from pydantic import ByteSize, HttpUrl, TypeAdapter from pytest_simcore.helpers.faker_factories import DEFAULT_FAKER from simcore_service_storage.constants import S3_UNDEFINED_OR_EXTERNAL_MULTIPART_ID diff --git a/services/web/server/src/simcore_service_webserver/projects/_nodes_api.py b/services/web/server/src/simcore_service_webserver/projects/_nodes_api.py index b371af80037..4f90324ac38 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_nodes_api.py +++ b/services/web/server/src/simcore_service_webserver/projects/_nodes_api.py @@ -7,11 +7,11 @@ from aiohttp import web from aiohttp.client import ClientError +from models_library.api_schemas_storage.rest.storage_schemas import FileMetaDataGet from models_library.basic_types import KeyIDStr from models_library.projects import ProjectID from models_library.projects_nodes import Node from models_library.projects_nodes_io import NodeID, SimCoreFileLink -from models_library.storage_schemas import FileMetaDataGet from models_library.users import UserID from pydantic import ( BaseModel, diff --git a/services/web/server/src/simcore_service_webserver/storage/_exception_handlers.py b/services/web/server/src/simcore_service_webserver/storage/_exception_handlers.py index d6cecb75d9e..1aec566f2c7 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_exception_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage/_exception_handlers.py @@ -1,4 +1,4 @@ -from models_library.api_schemas_storage.data_export_async_jobs import ( +from models_library.api_schemas_storage.rpc.data_export_async_jobs import ( AccessRightError, DataExportError, InvalidFileIdentifierError, diff --git a/services/web/server/src/simcore_service_webserver/storage/_handlers.py b/services/web/server/src/simcore_service_webserver/storage/_handlers.py index a43f7629d96..7a7b5d771f2 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage/_handlers.py @@ -10,6 +10,12 @@ from aiohttp import ClientTimeout, web from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE +from models_library.api_schemas_storage.rest.storage_schemas import ( + FileUploadCompleteResponse, + FileUploadCompletionBody, + FileUploadSchema, + LinkType, +) from models_library.api_schemas_webserver.storage import ( DataExportPost, StorageAsyncJobGet, @@ -18,12 +24,6 @@ ) from models_library.projects_nodes_io import LocationID from models_library.rest_base import RequestParameters -from models_library.storage_schemas import ( - FileUploadCompleteResponse, - FileUploadCompletionBody, - FileUploadSchema, - LinkType, -) from models_library.users import UserID from models_library.utils.fastapi_encoders import jsonable_encoder from pydantic import AnyUrl, BaseModel, ByteSize, Field, TypeAdapter diff --git a/services/web/server/src/simcore_service_webserver/storage/api.py b/services/web/server/src/simcore_service_webserver/storage/api.py index b4e77f46410..08efa0bc959 100644 --- a/services/web/server/src/simcore_service_webserver/storage/api.py +++ b/services/web/server/src/simcore_service_webserver/storage/api.py @@ -7,15 +7,15 @@ from typing import Any, Final from aiohttp import ClientError, ClientSession, ClientTimeout, web -from models_library.generics import Envelope -from models_library.projects import ProjectID -from models_library.projects_nodes_io import LocationID, NodeID, SimCoreFileLink -from models_library.storage_schemas import ( +from models_library.api_schemas_storage.rest.storage_schemas import ( FileLocation, FileLocationArray, FileMetaDataGet, PresignedLink, ) +from models_library.generics import Envelope +from models_library.projects import ProjectID +from models_library.projects_nodes_io import LocationID, NodeID, SimCoreFileLink from models_library.users import UserID from models_library.utils.fastapi_encoders import jsonable_encoder from pydantic import ByteSize, HttpUrl, TypeAdapter diff --git a/services/web/server/tests/unit/isolated/test_projects__nodes_api.py b/services/web/server/tests/unit/isolated/test_projects__nodes_api.py index 8c370cb2d3b..de7cc7dd8fb 100644 --- a/services/web/server/tests/unit/isolated/test_projects__nodes_api.py +++ b/services/web/server/tests/unit/isolated/test_projects__nodes_api.py @@ -2,7 +2,7 @@ from uuid import uuid4 import pytest -from models_library.storage_schemas import FileMetaDataGet +from models_library.api_schemas_storage.rest.storage_schemas import FileMetaDataGet from simcore_service_webserver.projects._nodes_api import ( _SUPPORTED_PREVIEW_FILE_EXTENSIONS, _FileWithThumbnail, diff --git a/services/web/server/tests/unit/with_dbs/01/test_storage.py b/services/web/server/tests/unit/with_dbs/01/test_storage.py index 5381dbb1962..c89a45d8ca8 100644 --- a/services/web/server/tests/unit/with_dbs/01/test_storage.py +++ b/services/web/server/tests/unit/with_dbs/01/test_storage.py @@ -15,10 +15,7 @@ from aiohttp.test_utils import TestClient from faker import Faker from fastapi import APIRouter, FastAPI, Request -from models_library.generics import Envelope -from models_library.projects import ProjectID -from models_library.projects_nodes_io import LocationID, StorageFileID -from models_library.storage_schemas import ( +from models_library.api_schemas_storage.rest.storage_schemas import ( DatasetMetaDataGet, FileLocation, FileMetaDataGet, @@ -28,6 +25,9 @@ FileUploadSchema, LinkType, ) +from models_library.generics import Envelope +from models_library.projects import ProjectID +from models_library.projects_nodes_io import LocationID, StorageFileID from models_library.users import UserID from pydantic import AnyUrl, TypeAdapter from pytest_simcore.helpers.assert_checks import assert_status diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_nodes_handler.py b/services/web/server/tests/unit/with_dbs/02/test_projects_nodes_handler.py index 10f8a6ee356..b0bf1e9a727 100644 --- a/services/web/server/tests/unit/with_dbs/02/test_projects_nodes_handler.py +++ b/services/web/server/tests/unit/with_dbs/02/test_projects_nodes_handler.py @@ -24,6 +24,10 @@ from models_library.api_schemas_dynamic_scheduler.dynamic_services import ( DynamicServiceStop, ) +from models_library.api_schemas_storage.rest.storage_schemas import ( + FileMetaDataGet, + PresignedLink, +) from models_library.generics import Envelope from models_library.projects_nodes_io import NodeID from models_library.services_resources import ( @@ -31,7 +35,6 @@ ServiceResourcesDict, ServiceResourcesDictHelpers, ) -from models_library.storage_schemas import FileMetaDataGet, PresignedLink from models_library.utils.fastapi_encoders import jsonable_encoder from pydantic import NonNegativeFloat, NonNegativeInt, TypeAdapter from pytest_simcore.helpers.assert_checks import assert_status diff --git a/services/web/server/tests/unit/with_dbs/03/test_storage_handlers.py b/services/web/server/tests/unit/with_dbs/03/test_storage_handlers.py index af1a6d3cbd3..660f243b7b0 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_storage_handlers.py +++ b/services/web/server/tests/unit/with_dbs/03/test_storage_handlers.py @@ -9,7 +9,7 @@ import pytest from aiohttp.test_utils import TestClient -from models_library.storage_schemas import ( +from models_library.api_schemas_storage.rest.storage_schemas import ( FileUploadCompleteResponse, FileUploadLinks, FileUploadSchema, diff --git a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py index daf38c5906b..8e825df29bc 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py +++ b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py @@ -16,13 +16,17 @@ ResultError, StatusError, ) -from models_library.api_schemas_storage.data_export_async_jobs import ( +from models_library.api_schemas_storage.rpc.data_export_async_jobs import ( AccessRightError, DataExportError, InvalidFileIdentifierError, ) +from models_library.api_schemas_webserver.storage import ( + DataExportPost, + StorageAsyncJobGet, +) from models_library.generics import Envelope -from models_library.storage_schemas import AsyncJobGet, DataExportPost +from models_library.progress_bar import ProgressReport from pytest_mock import MockerFixture from pytest_simcore.helpers.webserver_login import UserInfoDict from servicelib.aiohttp import status @@ -83,7 +87,7 @@ async def test_data_export( ) if isinstance(backend_result_or_exception, AsyncJobGet): assert response.status == status.HTTP_202_ACCEPTED - Envelope[AsyncJobGet].model_validate(await response.json()) + Envelope[StorageAsyncJobGet].model_validate(await response.json()) elif isinstance(backend_result_or_exception, InvalidFileIdentifierError): assert response.status == status.HTTP_404_NOT_FOUND elif isinstance(backend_result_or_exception, AccessRightError): @@ -99,7 +103,7 @@ async def test_data_export( [ AsyncJobStatus( job_id=_faker.uuid4(), - progress=0.5, + progress=ProgressReport(actual_value=0.5, total=1.0), done=False, started=datetime.now(), stopped=None, @@ -122,7 +126,7 @@ async def test_get_async_jobs_status( if isinstance(backend_result_or_exception, AsyncJobStatus): assert response.status == status.HTTP_200_OK response_body_data = ( - Envelope[AsyncJobGet].model_validate(await response.json()).data + Envelope[StorageAsyncJobGet].model_validate(await response.json()).data ) assert response_body_data is not None elif isinstance(backend_result_or_exception, StatusError): From b0f8a7f34969c1a7231a102e2752aacd446ba558 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Tue, 18 Feb 2025 15:59:23 +0100 Subject: [PATCH 36/63] =?UTF-8?q?services/webserver=20api=20version:=200.5?= =?UTF-8?q?8.0=20=E2=86=92=200.59.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 a60476bfe1c..cb6b534abe1 100644 --- a/services/web/server/VERSION +++ b/services/web/server/VERSION @@ -1 +1 @@ -0.58.0 +0.59.0 diff --git a/services/web/server/setup.cfg b/services/web/server/setup.cfg index 205ca5faab0..c15716c8df4 100644 --- a/services/web/server/setup.cfg +++ b/services/web/server/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.58.0 +current_version = 0.59.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 025b8118a1f..aa1bf99cb4e 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.58.0 + version: 0.59.0 servers: - url: '' description: webserver From 8acc41cda900c66fd7edac69013371b0a5ce61ca Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Tue, 18 Feb 2025 16:02:52 +0100 Subject: [PATCH 37/63] storage/_handlers.py -> storage/_rest.py --- .../storage/{_handlers.py => _rest.py} | 0 .../server/src/simcore_service_webserver/storage/plugin.py | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename services/web/server/src/simcore_service_webserver/storage/{_handlers.py => _rest.py} (100%) diff --git a/services/web/server/src/simcore_service_webserver/storage/_handlers.py b/services/web/server/src/simcore_service_webserver/storage/_rest.py similarity index 100% rename from services/web/server/src/simcore_service_webserver/storage/_handlers.py rename to services/web/server/src/simcore_service_webserver/storage/_rest.py diff --git a/services/web/server/src/simcore_service_webserver/storage/plugin.py b/services/web/server/src/simcore_service_webserver/storage/plugin.py index 104a9c37319..d1db7c23c57 100644 --- a/services/web/server/src/simcore_service_webserver/storage/plugin.py +++ b/services/web/server/src/simcore_service_webserver/storage/plugin.py @@ -9,7 +9,7 @@ from .._constants import APP_SETTINGS_KEY from ..rest.plugin import setup_rest -from . import _handlers +from . import _rest _logger = logging.getLogger(__name__) @@ -21,4 +21,4 @@ def setup_storage(app: web.Application): assert app[APP_SETTINGS_KEY].WEBSERVER_STORAGE # nosec setup_rest(app) - app.router.add_routes(_handlers.routes) + app.router.add_routes(_rest.routes) From c3758bd917c9fad046f8a71a711b9fb99b5008f2 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Tue, 18 Feb 2025 16:15:42 +0100 Subject: [PATCH 38/63] update opena api specs --- api/specs/web-server/_storage.py | 22 +- .../api/v0/openapi.yaml | 259 +++++++++++------- 2 files changed, 166 insertions(+), 115 deletions(-) diff --git a/api/specs/web-server/_storage.py b/api/specs/web-server/_storage.py index 86d3358f04a..9908da55ec6 100644 --- a/api/specs/web-server/_storage.py +++ b/api/specs/web-server/_storage.py @@ -9,10 +9,6 @@ from fastapi import APIRouter, Query, status from models_library.api_schemas_storage.rest.storage_schemas import ( - AsyncJobGet, - AsyncJobResult, - AsyncJobStatus, - DataExportPost, FileLocation, FileMetaDataGet, FileUploadCompleteFutureResponse, @@ -22,6 +18,12 @@ LinkType, PresignedLink, ) +from models_library.api_schemas_webserver.storage import ( + DataExportPost, + StorageAsyncJobGet, + StorageAsyncJobResult, + StorageAsyncJobStatus, +) from models_library.generics import Envelope from models_library.projects_nodes_io import LocationID from pydantic import AnyUrl, ByteSize @@ -177,7 +179,7 @@ async def is_completed_upload_file( # data export @router.post( "/storage/locations/{location_id}/export-data", - response_model=Envelope[AsyncJobGet], + response_model=Envelope[StorageAsyncJobGet], name="storage_export_data", description="Export data", ) @@ -187,10 +189,10 @@ async def export_data(data_export: DataExportPost, location_id: LocationID): @router.get( "/storage/async-jobs/{job_id}/status", - response_model=Envelope[AsyncJobStatus], + response_model=Envelope[StorageAsyncJobStatus], name="storage_async_job_status", ) -async def get_async_job_status(task_id: AsyncJobGet, job_id: UUID): +async def get_async_job_status(task_id: StorageAsyncJobGet, job_id: UUID): """Get async job status""" @@ -198,14 +200,14 @@ async def get_async_job_status(task_id: AsyncJobGet, job_id: UUID): "/storage/async-jobs/{job_id}:abort", name="abort_async_job", ) -async def abort_async_job(task_id: AsyncJobGet, job_id: UUID): +async def abort_async_job(task_id: StorageAsyncJobGet, job_id: UUID): """Get async job status""" @router.get( "/storage/async-jobs/{job_id}/result", - response_model=Envelope[AsyncJobResult], + response_model=Envelope[StorageAsyncJobResult], name="get_async_job_result", ) -async def get_async_job_result(task_id: AsyncJobGet, job_id: UUID): +async def get_async_job_result(task_id: StorageAsyncJobGet, job_id: UUID): """Get async job status""" 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 ac1b4a32e1f..bac2e61f282 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 @@ -6377,7 +6377,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Envelope_AsyncJobGet_' + $ref: '#/components/schemas/Envelope_StorageAsyncJobGet_' /v0/storage/async-jobs/{job_id}/status: get: tags: @@ -6398,14 +6398,14 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/AsyncJobGet' + $ref: '#/components/schemas/StorageAsyncJobGet' responses: '200': description: Successful Response content: application/json: schema: - $ref: '#/components/schemas/Envelope_AsyncJobStatus_' + $ref: '#/components/schemas/Envelope_StorageAsyncJobStatus_' /v0/storage/async-jobs/{job_id}:abort: post: tags: @@ -6426,7 +6426,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/AsyncJobGet' + $ref: '#/components/schemas/StorageAsyncJobGet' responses: '200': description: Successful Response @@ -6453,14 +6453,14 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/AsyncJobGet' + $ref: '#/components/schemas/StorageAsyncJobGet' responses: '200': description: Successful Response content: application/json: schema: - $ref: '#/components/schemas/Envelope_AsyncJobResult_' + $ref: '#/components/schemas/Envelope_StorageAsyncJobResult_' /v0/trash:empty: post: tags: @@ -7741,66 +7741,6 @@ components: - app_name - version title: AppStatusCheck - AsyncJobGet: - properties: - job_id: - type: string - format: uuid - title: Job Id - type: object - required: - - job_id - title: AsyncJobGet - AsyncJobResult: - properties: - result: - anyOf: - - {} - - type: 'null' - title: Result - error: - anyOf: - - {} - - type: 'null' - title: Error - type: object - required: - - result - - error - title: AsyncJobResult - AsyncJobStatus: - properties: - job_id: - type: string - format: uuid - title: Job Id - task_progress: - type: number - maximum: 1.0 - minimum: 0.0 - exclusiveMinimum: true - title: Task Progress - done: - type: boolean - title: Done - started: - type: string - format: date-time - title: Started - stopped: - anyOf: - - type: string - format: date-time - - type: 'null' - title: Stopped - type: object - required: - - job_id - - task_progress - - done - - started - - stopped - title: AsyncJobStatus Author: properties: name: @@ -8577,45 +8517,6 @@ components: title: Error type: object title: Envelope[AppStatusCheck] - Envelope_AsyncJobGet_: - properties: - data: - anyOf: - - $ref: '#/components/schemas/AsyncJobGet' - - type: 'null' - error: - anyOf: - - {} - - type: 'null' - title: Error - type: object - title: Envelope[AsyncJobGet] - Envelope_AsyncJobResult_: - properties: - data: - anyOf: - - $ref: '#/components/schemas/AsyncJobResult' - - type: 'null' - error: - anyOf: - - {} - - type: 'null' - title: Error - type: object - title: Envelope[AsyncJobResult] - Envelope_AsyncJobStatus_: - properties: - data: - anyOf: - - $ref: '#/components/schemas/AsyncJobStatus' - - type: 'null' - error: - anyOf: - - {} - - type: 'null' - title: Error - type: object - title: Envelope[AsyncJobStatus] Envelope_CatalogServiceGet_: properties: data: @@ -9188,6 +9089,45 @@ components: title: Error type: object title: Envelope[StatusDiagnosticsGet] + Envelope_StorageAsyncJobGet_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/StorageAsyncJobGet' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[StorageAsyncJobGet] + Envelope_StorageAsyncJobResult_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/StorageAsyncJobResult' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[StorageAsyncJobResult] + Envelope_StorageAsyncJobStatus_: + properties: + data: + anyOf: + - $ref: '#/components/schemas/StorageAsyncJobStatus' + - type: 'null' + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[StorageAsyncJobStatus] Envelope_TagGet_: properties: data: @@ -12940,6 +12880,59 @@ components: - productName - ui title: ProductUIGet + ProgressReport: + properties: + actual_value: + type: number + title: Actual Value + total: + type: number + title: Total + default: 1.0 + attempt: + type: integer + title: Attempt + default: 0 + unit: + anyOf: + - type: string + const: Byte + - type: 'null' + title: Unit + message: + anyOf: + - $ref: '#/components/schemas/ProgressStructuredMessage' + - type: 'null' + type: object + required: + - actual_value + title: ProgressReport + ProgressStructuredMessage: + properties: + description: + type: string + title: Description + current: + type: number + title: Current + total: + type: integer + title: Total + unit: + anyOf: + - type: string + - type: 'null' + title: Unit + sub: + anyOf: + - $ref: '#/components/schemas/ProgressStructuredMessage' + - type: 'null' + type: object + required: + - description + - current + - total + title: ProgressStructuredMessage ProjectCopyOverride: properties: name: @@ -14554,6 +14547,62 @@ components: - loop_tasks - top_tracemalloc title: StatusDiagnosticsGet + StorageAsyncJobGet: + properties: + job_id: + type: string + format: uuid + title: Job Id + type: object + required: + - job_id + title: StorageAsyncJobGet + StorageAsyncJobResult: + properties: + result: + anyOf: + - {} + - type: 'null' + title: Result + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + required: + - result + - error + title: StorageAsyncJobResult + StorageAsyncJobStatus: + properties: + job_id: + type: string + format: uuid + title: Job Id + progress: + $ref: '#/components/schemas/ProgressReport' + done: + type: boolean + title: Done + started: + type: string + format: date-time + title: Started + stopped: + anyOf: + - type: string + format: date-time + - type: 'null' + title: Stopped + type: object + required: + - job_id + - progress + - done + - started + - stopped + title: StorageAsyncJobStatus Structure: properties: key: From ccff359ddbfe381c724b32f01b527a2f7b8b2a24 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Feb 2025 06:11:06 +0100 Subject: [PATCH 39/63] @pcrespov streamline to_rpc_schema methods --- .../rpc/data_export_async_jobs.py | 8 +++---- .../api_schemas_webserver/storage.py | 21 ++++++++----------- .../storage/_rest.py | 8 +++---- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/packages/models-library/src/models_library/api_schemas_storage/rpc/data_export_async_jobs.py b/packages/models-library/src/models_library/api_schemas_storage/rpc/data_export_async_jobs.py index 02162a0b42e..528f40b78e1 100644 --- a/packages/models-library/src/models_library/api_schemas_storage/rpc/data_export_async_jobs.py +++ b/packages/models-library/src/models_library/api_schemas_storage/rpc/data_export_async_jobs.py @@ -16,17 +16,17 @@ class DataExportTaskStartInput(BaseModel): ### Exceptions -class StorageRpcErrors(OsparcErrorMixin, RuntimeError): +class StorageRpcError(OsparcErrorMixin, RuntimeError): pass -class InvalidFileIdentifierError(StorageRpcErrors): +class InvalidFileIdentifierError(StorageRpcError): msg_template: str = "Could not find the file {file_id}" -class AccessRightError(StorageRpcErrors): +class AccessRightError(StorageRpcError): msg_template: str = "User {user_id} does not have access to file {file_id}" -class DataExportError(StorageRpcErrors): +class DataExportError(StorageRpcError): msg_template: str = "Could not complete data export job with id {job_id}" diff --git a/packages/models-library/src/models_library/api_schemas_webserver/storage.py b/packages/models-library/src/models_library/api_schemas_webserver/storage.py index 557332396a9..c5966710fef 100644 --- a/packages/models-library/src/models_library/api_schemas_webserver/storage.py +++ b/packages/models-library/src/models_library/api_schemas_webserver/storage.py @@ -2,8 +2,6 @@ from pathlib import Path from typing import Any -from pydantic import BaseModel - from ..api_schemas_rpc_async_jobs.async_jobs import ( AsyncJobGet, AsyncJobId, @@ -14,12 +12,13 @@ from ..progress_bar import ProgressReport from ..projects_nodes_io import LocationID from ..users import UserID +from ._base import InputSchema, OutputSchema -class DataExportPost(BaseModel): +class DataExportPost(InputSchema): paths: list[Path] - def to_storage_model( + def to_rpc_schema( self, user_id: UserID, location_id: LocationID ) -> DataExportTaskStartInput: return DataExportTaskStartInput( @@ -27,17 +26,15 @@ def to_storage_model( ) -class StorageAsyncJobGet(BaseModel): +class StorageAsyncJobGet(OutputSchema): job_id: AsyncJobId @classmethod - def from_async_job_rpc_get( - cls, async_job_rpc_get: AsyncJobGet - ) -> "StorageAsyncJobGet": + def from_rpc_schema(cls, async_job_rpc_get: AsyncJobGet) -> "StorageAsyncJobGet": return StorageAsyncJobGet(job_id=async_job_rpc_get.job_id) -class StorageAsyncJobStatus(BaseModel): +class StorageAsyncJobStatus(OutputSchema): job_id: AsyncJobId progress: ProgressReport done: bool @@ -45,7 +42,7 @@ class StorageAsyncJobStatus(BaseModel): stopped: datetime | None @classmethod - def from_async_job_rpc_status( + def from_rpc_schema( cls, async_job_rpc_status: AsyncJobStatus ) -> "StorageAsyncJobStatus": return StorageAsyncJobStatus( @@ -57,12 +54,12 @@ def from_async_job_rpc_status( ) -class StorageAsyncJobResult(BaseModel): +class StorageAsyncJobResult(OutputSchema): result: Any | None error: Any | None @classmethod - def from_async_job_rpc_result( + def from_rpc_schema( cls, async_job_rpc_result: AsyncJobResult ) -> "StorageAsyncJobResult": return StorageAsyncJobResult( diff --git a/services/web/server/src/simcore_service_webserver/storage/_rest.py b/services/web/server/src/simcore_service_webserver/storage/_rest.py index 7a7b5d771f2..24ec4b44f87 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_rest.py +++ b/services/web/server/src/simcore_service_webserver/storage/_rest.py @@ -402,12 +402,12 @@ class _PathParams(BaseModel): ) async_job_rpc_get = await start_data_export( rabbitmq_rpc_client=rabbitmq_rpc_client, - paths=data_export_post.to_storage_model( + paths=data_export_post.to_rpc_schema( user_id=_req_ctx.user_id, location_id=_path_params.location_id ), ) return create_data_response( - StorageAsyncJobGet.from_async_job_rpc_get(async_job_rpc_get), + StorageAsyncJobGet.from_rpc_schema(async_job_rpc_get), status=status.HTTP_202_ACCEPTED, ) @@ -429,7 +429,7 @@ async def get_async_job_status(request: web.Request) -> web.Response: job_id=async_job_get.job_id, ) return create_data_response( - StorageAsyncJobStatus.from_async_job_rpc_status(async_job_rpc_status), + StorageAsyncJobStatus.from_rpc_schema(async_job_rpc_status), status=status.HTTP_200_OK, ) @@ -472,6 +472,6 @@ async def get_async_job_result(request: web.Request) -> web.Response: job_id=async_job_get.job_id, ) return create_data_response( - StorageAsyncJobResult.from_async_job_rpc_result(async_job_rpc_result), + StorageAsyncJobResult.from_rpc_schema(async_job_rpc_result), status=status.HTTP_200_OK, ) From b01e479f57e0fa8f89f2727e5b26a3df1de478ea Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Feb 2025 06:38:51 +0100 Subject: [PATCH 40/63] add rpc method for getting jobs associated with user --- .../api_schemas_rpc_async_jobs/async_jobs.py | 2 +- .../rabbitmq/rpc_interfaces/storage/data_export.py | 13 +++++++++++++ .../simcore_service_storage/api/rpc/_data_export.py | 10 +++++++++- services/storage/tests/unit/test_data_export.py | 8 ++++++++ .../tests/unit/with_dbs/03/test_storage_rpc.py | 2 +- 5 files changed, 32 insertions(+), 3 deletions(-) diff --git a/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py b/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py index d1b60ea032c..917b397a15e 100644 --- a/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py +++ b/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py @@ -35,7 +35,7 @@ class AsyncJobResult(BaseModel): class AsyncJobGet(BaseModel): job_id: AsyncJobId - task_name: str + job_name: str class AsyncJobAbort(BaseModel): diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py index 503649042e3..f35eae73a8f 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py @@ -6,6 +6,7 @@ DataExportTaskStartInput, ) from models_library.rabbitmq_basic_types import RPCMethodName +from models_library.users import UserID from pydantic import NonNegativeInt, TypeAdapter from ... import RabbitMQRPCClient @@ -26,3 +27,15 @@ async def start_data_export( ) assert isinstance(result, AsyncJobGet) return result + + +async def get_user_jobs( + rabbitmq_rpc_client: RabbitMQRPCClient, *, user_id: UserID +) -> list[AsyncJobGet]: + result: list[AsyncJobGet] = await rabbitmq_rpc_client.request( + STORAGE_RPC_NAMESPACE, + _RPC_METHOD_NAME_ADAPTER.validate_python("get_user_jobs"), + user_id=user_id, + timeout_s=_DEFAULT_TIMEOUT_S, + ) + return result diff --git a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py index 2dc5b7fd6cd..962a2708ff1 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py @@ -8,6 +8,7 @@ DataExportTaskStartInput, InvalidFileIdentifierError, ) +from models_library.users import UserID from servicelib.rabbitmq import RPCRouter router = RPCRouter() @@ -26,5 +27,12 @@ async def start_data_export( assert app # nosec return AsyncJobGet( job_id=uuid4(), - task_name=", ".join(str(p) for p in paths.paths), + job_name=", ".join(str(p) for p in paths.paths), ) + + +@router.expose() +async def get_user_jobs(app: FastAPI, user_id: UserID) -> list[AsyncJobGet]: + assert app # nosec + assert user_id # nosec + return [AsyncJobGet(job_id=uuid4(), job_name="myjob")] diff --git a/services/storage/tests/unit/test_data_export.py b/services/storage/tests/unit/test_data_export.py index e5ccdc26fbd..cebc0e85437 100644 --- a/services/storage/tests/unit/test_data_export.py +++ b/services/storage/tests/unit/test_data_export.py @@ -109,3 +109,11 @@ async def test_get_data_export_result(rpc_client: RabbitMQRPCClient, faker: Fake rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, job_id=_job_id ) assert isinstance(result, AsyncJobResult) + + +async def test_get_user_jobs(rpc_client: RabbitMQRPCClient, faker: Faker): + result = await data_export.get_user_jobs( + rpc_client, user_id=faker.pyint(min_value=1) + ) + assert isinstance(result, list) + assert all(isinstance(elm, AsyncJobGet) for elm in result) diff --git a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py index 8e825df29bc..6ac1e89c861 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py +++ b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py @@ -61,7 +61,7 @@ def side_effect(*args, **kwargs): @pytest.mark.parametrize( "backend_result_or_exception", [ - AsyncJobGet(job_id=AsyncJobId(_faker.uuid4()), task_name=_faker.text()), + AsyncJobGet(job_id=AsyncJobId(_faker.uuid4()), job_name=_faker.text()), InvalidFileIdentifierError(file_id=Path("/my/file")), AccessRightError(user_id=_faker.pyint(min_value=0), file_id=Path("/my/file")), DataExportError(job_id=_faker.pyint(min_value=0)), From eec16d8f40320c34b31f4fa24329b590af4077c4 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Feb 2025 06:48:24 +0100 Subject: [PATCH 41/63] add webserver rest endpoint for getting user jobs --- .../storage/_rest.py | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/services/web/server/src/simcore_service_webserver/storage/_rest.py b/services/web/server/src/simcore_service_webserver/storage/_rest.py index 24ec4b44f87..5b1e3ed72c1 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_rest.py +++ b/services/web/server/src/simcore_service_webserver/storage/_rest.py @@ -41,7 +41,10 @@ get_result, get_status, ) -from servicelib.rabbitmq.rpc_interfaces.storage.data_export import start_data_export +from servicelib.rabbitmq.rpc_interfaces.storage.data_export import ( + get_user_jobs, + start_data_export, +) from servicelib.request_keys import RQT_USERID_KEY from servicelib.rest_responses import unwrap_envelope from simcore_service_webserver.rabbitmq import get_rabbitmq_rpc_client @@ -412,6 +415,30 @@ class _PathParams(BaseModel): ) +@routes.get( + _storage_prefix + "/async-jobs", + name="get_async_jobs", +) +@login_required +@permission_required("storage.files.*") +@handle_data_export_exceptions +async def get_async_jobs(request: web.Request) -> web.Response: + class _RequestContext(RequestParameters): + user_id: UserID = Field(..., alias=RQT_USERID_KEY) # type: ignore[literal-required] + + _req_ctx = _RequestContext.model_validate(request) + + rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) + + user_async_jobs = await get_user_jobs( + rabbitmq_rpc_client=rabbitmq_rpc_client, user_id=_req_ctx.user_id + ) + return create_data_response( + [StorageAsyncJobGet.from_rpc_schema(job) for job in user_async_jobs], + status=status.HTTP_200_OK, + ) + + @routes.get( _storage_prefix + "/async-jobs/{job_id}/status", name="get_async_job_status", From 432dbb3202dfc5e23f1675a595efd757d66ebaba Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Feb 2025 07:01:25 +0100 Subject: [PATCH 42/63] add test of rest endpoint for getting jobs associated with user --- .../unit/with_dbs/03/test_storage_rpc.py | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py index 6ac1e89c861..a45fd33ae6d 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py +++ b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py @@ -35,7 +35,10 @@ get_result, get_status, ) -from servicelib.rabbitmq.rpc_interfaces.storage.data_export import start_data_export +from servicelib.rabbitmq.rpc_interfaces.storage.data_export import ( + get_user_jobs, + start_data_export, +) from simcore_postgres_database.models.users import UserRole _faker = Faker() @@ -50,7 +53,7 @@ def side_effect(*args, **kwargs): return result_or_exception mocker.patch( - f"simcore_service_webserver.storage._handlers.{method}", + f"simcore_service_webserver.storage._rest.{method}", side_effect=side_effect, ) @@ -186,3 +189,26 @@ async def test_get_async_job_result( assert response.status == status.HTTP_500_INTERNAL_SERVER_ERROR else: raise Exception("Test incorrectly configured") + + +@pytest.mark.parametrize("user_role", [UserRole.USER]) +@pytest.mark.parametrize( + "backend_result_or_exception", + [ + [StorageAsyncJobGet(job_id=AsyncJobId(_faker.uuid4()))], + ], + ids=lambda x: type(x).__name__, +) +async def test_get_user_async_jobs( + user_role: UserRole, + logged_user: UserInfoDict, + client: TestClient, + create_storage_rpc_client_mock: Callable[[str, Any], None], + backend_result_or_exception: Any, +): + create_storage_rpc_client_mock(get_user_jobs.__name__, backend_result_or_exception) + + response = await client.get("/v0/storage/async-jobs") + + assert response.status == status.HTTP_200_OK + Envelope[list[StorageAsyncJobGet]].model_validate(await response.json()) From 8e6162d06fa225427bc7d35ffac75f6ee6f89d80 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Feb 2025 09:44:40 +0100 Subject: [PATCH 43/63] minor cleanup --- .../tests/unit/with_dbs/03/test_storage_rpc.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py index a45fd33ae6d..c69139f9bf9 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py +++ b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py @@ -192,21 +192,15 @@ async def test_get_async_job_result( @pytest.mark.parametrize("user_role", [UserRole.USER]) -@pytest.mark.parametrize( - "backend_result_or_exception", - [ - [StorageAsyncJobGet(job_id=AsyncJobId(_faker.uuid4()))], - ], - ids=lambda x: type(x).__name__, -) async def test_get_user_async_jobs( user_role: UserRole, logged_user: UserInfoDict, client: TestClient, create_storage_rpc_client_mock: Callable[[str, Any], None], - backend_result_or_exception: Any, ): - create_storage_rpc_client_mock(get_user_jobs.__name__, backend_result_or_exception) + create_storage_rpc_client_mock( + get_user_jobs.__name__, [StorageAsyncJobGet(job_id=AsyncJobId(_faker.uuid4()))] + ) response = await client.get("/v0/storage/async-jobs") From f817a525da0cd86cd33dd08c97a888123bd5b4b6 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Feb 2025 10:45:45 +0100 Subject: [PATCH 44/63] move thing around --- api/specs/web-server/_storage.py | 2 +- packages/aws-library/src/aws_library/s3/_client.py | 2 +- packages/aws-library/src/aws_library/s3/_models.py | 2 +- packages/aws-library/tests/test_s3_client.py | 2 +- .../{rpc => }/data_export_async_jobs.py | 0 .../api_schemas_storage/{rest => }/storage_schemas.py | 8 ++++---- .../src/models_library/api_schemas_webserver/storage.py | 2 +- packages/pytest-simcore/src/pytest_simcore/helpers/s3.py | 2 +- .../services_api_mocks_for_aiohttp_clients.py | 2 +- .../rabbitmq/rpc_interfaces/storage/data_export.py | 2 +- .../simcore_sdk/node_ports_common/_filemanager_utils.py | 2 +- .../src/simcore_sdk/node_ports_common/file_io_utils.py | 2 +- .../src/simcore_sdk/node_ports_common/filemanager.py | 2 +- .../src/simcore_sdk/node_ports_common/storage_client.py | 2 +- .../simcore-sdk/src/simcore_sdk/node_ports_v2/__init__.py | 4 +--- .../src/simcore_sdk/node_ports_v2/nodeports_v2.py | 2 +- .../simcore-sdk/src/simcore_sdk/node_ports_v2/port.py | 2 +- .../src/simcore_sdk/node_ports_v2/port_utils.py | 2 +- packages/simcore-sdk/tests/integration/conftest.py | 2 +- .../tests/unit/test_node_ports_common_file_io_utils.py | 2 +- .../simcore-sdk/tests/unit/test_node_ports_v2_port.py | 2 +- packages/simcore-sdk/tests/unit/test_storage_client.py | 2 +- .../src/simcore_service_api_server/api/routes/files.py | 2 +- .../simcore_service_api_server/models/schemas/files.py | 2 +- .../simcore_service_api_server/services_http/storage.py | 6 +++--- services/api-server/tests/unit/conftest.py | 2 +- services/api-server/tests/unit/test_api_files.py | 2 +- .../api-server/tests/unit/test_models_schemas_files.py | 2 +- .../director-v2/tests/unit/with_dbs/test_utils_dask.py | 2 +- .../tests/integration/test_modules_long_running_tasks.py | 2 +- .../services/modules/db/service_runs_db.py | 2 +- .../services/modules/s3.py | 2 +- .../services/service_runs.py | 2 +- .../src/simcore_service_storage/api/rest/_datasets.py | 2 +- .../src/simcore_service_storage/api/rest/_files.py | 2 +- .../src/simcore_service_storage/api/rest/_health.py | 5 +---- .../src/simcore_service_storage/api/rest/_locations.py | 2 +- .../src/simcore_service_storage/api/rest/_simcore_s3.py | 2 +- .../src/simcore_service_storage/api/rpc/_data_export.py | 2 +- services/storage/src/simcore_service_storage/constants.py | 2 +- .../storage/src/simcore_service_storage/datcore_dsm.py | 2 +- .../storage/src/simcore_service_storage/dsm_factory.py | 5 +---- services/storage/src/simcore_service_storage/models.py | 2 +- .../modules/datcore_adapter/datcore_adapter.py | 2 +- .../storage/src/simcore_service_storage/simcore_s3_dsm.py | 2 +- .../simcore_service_storage/utils/simcore_s3_dsm_utils.py | 2 +- services/storage/tests/conftest.py | 2 +- services/storage/tests/unit/test_data_export.py | 2 +- services/storage/tests/unit/test_dsm_dsmcleaner.py | 2 +- services/storage/tests/unit/test_dsm_soft_links.py | 2 +- services/storage/tests/unit/test_handlers_datasets.py | 2 +- services/storage/tests/unit/test_handlers_files.py | 2 +- .../storage/tests/unit/test_handlers_files_metadata.py | 2 +- services/storage/tests/unit/test_handlers_health.py | 5 +---- services/storage/tests/unit/test_handlers_simcore_s3.py | 2 +- services/storage/tests/unit/test_models.py | 2 +- services/storage/tests/unit/test_simcore_s3_dsm.py | 2 +- services/storage/tests/unit/test_utils.py | 2 +- .../src/simcore_service_webserver/projects/_nodes_api.py | 2 +- .../storage/_exception_handlers.py | 2 +- .../server/src/simcore_service_webserver/storage/_rest.py | 2 +- .../server/src/simcore_service_webserver/storage/api.py | 2 +- .../tests/unit/isolated/test_projects__nodes_api.py | 2 +- .../web/server/tests/unit/with_dbs/01/test_storage.py | 2 +- .../tests/unit/with_dbs/02/test_projects_nodes_handler.py | 2 +- .../tests/unit/with_dbs/03/test_storage_handlers.py | 2 +- .../web/server/tests/unit/with_dbs/03/test_storage_rpc.py | 2 +- 67 files changed, 71 insertions(+), 82 deletions(-) rename packages/models-library/src/models_library/api_schemas_storage/{rpc => }/data_export_async_jobs.py (100%) rename packages/models-library/src/models_library/api_schemas_storage/{rest => }/storage_schemas.py (98%) diff --git a/api/specs/web-server/_storage.py b/api/specs/web-server/_storage.py index 9908da55ec6..05f8951dcda 100644 --- a/api/specs/web-server/_storage.py +++ b/api/specs/web-server/_storage.py @@ -8,7 +8,7 @@ from uuid import UUID from fastapi import APIRouter, Query, status -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( FileLocation, FileMetaDataGet, FileUploadCompleteFutureResponse, diff --git a/packages/aws-library/src/aws_library/s3/_client.py b/packages/aws-library/src/aws_library/s3/_client.py index 55483ff546f..e62d55d2791 100644 --- a/packages/aws-library/src/aws_library/s3/_client.py +++ b/packages/aws-library/src/aws_library/s3/_client.py @@ -13,7 +13,7 @@ from boto3.s3.transfer import TransferConfig from botocore import exceptions as botocore_exc from botocore.client import Config -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( ETag, S3BucketName, UploadedPart, diff --git a/packages/aws-library/src/aws_library/s3/_models.py b/packages/aws-library/src/aws_library/s3/_models.py index 052859d3894..f02a4765fad 100644 --- a/packages/aws-library/src/aws_library/s3/_models.py +++ b/packages/aws-library/src/aws_library/s3/_models.py @@ -2,7 +2,7 @@ from dataclasses import dataclass from typing import TypeAlias -from models_library.api_schemas_storage.rest.storage_schemas import ETag +from models_library.api_schemas_storage.storage_schemas import ETag from models_library.basic_types import SHA256Str from pydantic import AnyUrl, BaseModel, ByteSize from types_aiobotocore_s3.type_defs import HeadObjectOutputTypeDef, ObjectTypeDef diff --git a/packages/aws-library/tests/test_s3_client.py b/packages/aws-library/tests/test_s3_client.py index 4f8b5bbe395..cca7a19fd78 100644 --- a/packages/aws-library/tests/test_s3_client.py +++ b/packages/aws-library/tests/test_s3_client.py @@ -38,7 +38,7 @@ ) from aws_library.s3._models import MultiPartUploadLinks from faker import Faker -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( S3BucketName, UploadedPart, ) diff --git a/packages/models-library/src/models_library/api_schemas_storage/rpc/data_export_async_jobs.py b/packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py similarity index 100% rename from packages/models-library/src/models_library/api_schemas_storage/rpc/data_export_async_jobs.py rename to packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py diff --git a/packages/models-library/src/models_library/api_schemas_storage/rest/storage_schemas.py b/packages/models-library/src/models_library/api_schemas_storage/storage_schemas.py similarity index 98% rename from packages/models-library/src/models_library/api_schemas_storage/rest/storage_schemas.py rename to packages/models-library/src/models_library/api_schemas_storage/storage_schemas.py index f5388365474..2faa2462cfd 100644 --- a/packages/models-library/src/models_library/api_schemas_storage/rest/storage_schemas.py +++ b/packages/models-library/src/models_library/api_schemas_storage/storage_schemas.py @@ -28,10 +28,10 @@ ) from pydantic.networks import AnyUrl -from ...basic_regex import DATCORE_DATASET_NAME_RE, S3_BUCKET_NAME_RE -from ...basic_types import SHA256Str -from ...generics import ListModel -from ...projects_nodes_io import ( +from ..basic_regex import DATCORE_DATASET_NAME_RE, S3_BUCKET_NAME_RE +from ..basic_types import SHA256Str +from ..generics import ListModel +from ..projects_nodes_io import ( LocationID, LocationName, NodeID, diff --git a/packages/models-library/src/models_library/api_schemas_webserver/storage.py b/packages/models-library/src/models_library/api_schemas_webserver/storage.py index c5966710fef..d623396a6a3 100644 --- a/packages/models-library/src/models_library/api_schemas_webserver/storage.py +++ b/packages/models-library/src/models_library/api_schemas_webserver/storage.py @@ -8,7 +8,7 @@ AsyncJobResult, AsyncJobStatus, ) -from ..api_schemas_storage.rpc.data_export_async_jobs import DataExportTaskStartInput +from ..api_schemas_storage.data_export_async_jobs import DataExportTaskStartInput from ..progress_bar import ProgressReport from ..projects_nodes_io import LocationID from ..users import UserID diff --git a/packages/pytest-simcore/src/pytest_simcore/helpers/s3.py b/packages/pytest-simcore/src/pytest_simcore/helpers/s3.py index 52420362dd9..61d630d994c 100644 --- a/packages/pytest-simcore/src/pytest_simcore/helpers/s3.py +++ b/packages/pytest-simcore/src/pytest_simcore/helpers/s3.py @@ -8,7 +8,7 @@ import orjson from aws_library.s3 import MultiPartUploadLinks from fastapi import status -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( ETag, FileUploadSchema, UploadedPart, diff --git a/packages/pytest-simcore/src/pytest_simcore/services_api_mocks_for_aiohttp_clients.py b/packages/pytest-simcore/src/pytest_simcore/services_api_mocks_for_aiohttp_clients.py index 303cf27c36d..da657de6917 100644 --- a/packages/pytest-simcore/src/pytest_simcore/services_api_mocks_for_aiohttp_clients.py +++ b/packages/pytest-simcore/src/pytest_simcore/services_api_mocks_for_aiohttp_clients.py @@ -12,7 +12,7 @@ from aioresponses.core import CallbackResult from faker import Faker from models_library.api_schemas_directorv2.comp_tasks import ComputationGet -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( FileMetaDataGet, FileUploadCompleteFutureResponse, FileUploadCompleteResponse, diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py index f35eae73a8f..ceae1e57195 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py @@ -2,7 +2,7 @@ from models_library.api_schemas_rpc_async_jobs.async_jobs import AsyncJobGet from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE -from models_library.api_schemas_storage.rpc.data_export_async_jobs import ( +from models_library.api_schemas_storage.data_export_async_jobs import ( DataExportTaskStartInput, ) from models_library.rabbitmq_basic_types import RPCMethodName diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports_common/_filemanager_utils.py b/packages/simcore-sdk/src/simcore_sdk/node_ports_common/_filemanager_utils.py index 208ff9ff344..08bd0a8cf97 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports_common/_filemanager_utils.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports_common/_filemanager_utils.py @@ -2,7 +2,7 @@ from typing import cast from aiohttp import ClientError, ClientSession -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( ETag, FileUploadCompleteFutureResponse, FileUploadCompleteResponse, diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports_common/file_io_utils.py b/packages/simcore-sdk/src/simcore_sdk/node_ports_common/file_io_utils.py index 3d9cab2c1c2..7f6801043cd 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports_common/file_io_utils.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports_common/file_io_utils.py @@ -17,7 +17,7 @@ ClientSession, RequestInfo, ) -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( ETag, FileUploadSchema, UploadedPart, diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports_common/filemanager.py b/packages/simcore-sdk/src/simcore_sdk/node_ports_common/filemanager.py index b29bc8e7bea..46b8444fde9 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports_common/filemanager.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports_common/filemanager.py @@ -5,7 +5,7 @@ import aiofiles from aiohttp import ClientSession -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( ETag, FileMetaDataGet, FileUploadSchema, diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports_common/storage_client.py b/packages/simcore-sdk/src/simcore_sdk/node_ports_common/storage_client.py index f8a7cb2b378..71d80febbc0 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports_common/storage_client.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports_common/storage_client.py @@ -10,7 +10,7 @@ from aiohttp import ClientResponse, ClientSession from aiohttp import client as aiohttp_client_module from aiohttp.client_exceptions import ClientConnectionError, ClientResponseError -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( FileLocationArray, FileMetaDataGet, FileUploadSchema, diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/__init__.py b/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/__init__.py index b62b45235c0..8874f98efe7 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/__init__.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/__init__.py @@ -1,8 +1,6 @@ import logging -from models_library.api_schemas_storage.rest.storage_schemas import ( - LinkType as FileLinkType, -) +from models_library.api_schemas_storage.storage_schemas import LinkType as FileLinkType from models_library.projects import ProjectIDStr from models_library.projects_nodes_io import NodeIDStr from models_library.users import UserID diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/nodeports_v2.py b/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/nodeports_v2.py index b5e23bce0c5..59c73716ca3 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/nodeports_v2.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/nodeports_v2.py @@ -6,7 +6,7 @@ from pathlib import Path from typing import Any -from models_library.api_schemas_storage.rest.storage_schemas import LinkType +from models_library.api_schemas_storage.storage_schemas import LinkType from models_library.projects import ProjectIDStr from models_library.projects_nodes_io import NodeIDStr from models_library.services_types import ServicePortKey diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/port.py b/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/port.py index ee88602e4db..014aff56529 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/port.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/port.py @@ -6,7 +6,7 @@ from pprint import pformat from typing import Any -from models_library.api_schemas_storage.rest.storage_schemas import LinkType +from models_library.api_schemas_storage.storage_schemas import LinkType from models_library.services_io import BaseServiceIOModel from models_library.services_types import ServicePortKey from pydantic import ( diff --git a/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/port_utils.py b/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/port_utils.py index 93705297f95..41f317b6f44 100644 --- a/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/port_utils.py +++ b/packages/simcore-sdk/src/simcore_sdk/node_ports_v2/port_utils.py @@ -4,7 +4,7 @@ from pathlib import Path from typing import Any -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( FileUploadSchema, LinkType, ) diff --git a/packages/simcore-sdk/tests/integration/conftest.py b/packages/simcore-sdk/tests/integration/conftest.py index e9195dcf986..b3aba7d8d35 100644 --- a/packages/simcore-sdk/tests/integration/conftest.py +++ b/packages/simcore-sdk/tests/integration/conftest.py @@ -13,7 +13,7 @@ import pytest import sqlalchemy as sa from aiohttp import ClientSession -from models_library.api_schemas_storage.rest.storage_schemas import FileUploadSchema +from models_library.api_schemas_storage.storage_schemas import FileUploadSchema from models_library.generics import Envelope from models_library.projects_nodes_io import LocationID, NodeIDStr, SimcoreS3FileID from models_library.users import UserID diff --git a/packages/simcore-sdk/tests/unit/test_node_ports_common_file_io_utils.py b/packages/simcore-sdk/tests/unit/test_node_ports_common_file_io_utils.py index 49772ed2a4c..70ad8adbbc7 100644 --- a/packages/simcore-sdk/tests/unit/test_node_ports_common_file_io_utils.py +++ b/packages/simcore-sdk/tests/unit/test_node_ports_common_file_io_utils.py @@ -13,7 +13,7 @@ from aiohttp import ClientResponse, ClientSession, TCPConnector from aioresponses import aioresponses from faker import Faker -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( FileUploadLinks, FileUploadSchema, UploadedPart, diff --git a/packages/simcore-sdk/tests/unit/test_node_ports_v2_port.py b/packages/simcore-sdk/tests/unit/test_node_ports_v2_port.py index c507bb9c0e6..516c828266f 100644 --- a/packages/simcore-sdk/tests/unit/test_node_ports_v2_port.py +++ b/packages/simcore-sdk/tests/unit/test_node_ports_v2_port.py @@ -21,7 +21,7 @@ from aiohttp.client import ClientSession from aioresponses import aioresponses as AioResponsesMock from faker import Faker -from models_library.api_schemas_storage.rest.storage_schemas import FileMetaDataGet +from models_library.api_schemas_storage.storage_schemas import FileMetaDataGet from models_library.projects_nodes_io import LocationID from pydantic import TypeAdapter, ValidationError from pytest_mock.plugin import MockerFixture diff --git a/packages/simcore-sdk/tests/unit/test_storage_client.py b/packages/simcore-sdk/tests/unit/test_storage_client.py index c91f356756e..b02c8b2244b 100644 --- a/packages/simcore-sdk/tests/unit/test_storage_client.py +++ b/packages/simcore-sdk/tests/unit/test_storage_client.py @@ -12,7 +12,7 @@ import pytest from aioresponses import aioresponses as AioResponsesMock from faker import Faker -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( FileLocationArray, FileMetaDataGet, FileUploadSchema, diff --git a/services/api-server/src/simcore_service_api_server/api/routes/files.py b/services/api-server/src/simcore_service_api_server/api/routes/files.py index 40031eff706..0821d81abab 100644 --- a/services/api-server/src/simcore_service_api_server/api/routes/files.py +++ b/services/api-server/src/simcore_service_api_server/api/routes/files.py @@ -10,7 +10,7 @@ from fastapi import Header, Request, UploadFile, status from fastapi.exceptions import HTTPException from fastapi_pagination.api import create_page -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( ETag, FileUploadCompletionBody, LinkType, diff --git a/services/api-server/src/simcore_service_api_server/models/schemas/files.py b/services/api-server/src/simcore_service_api_server/models/schemas/files.py index 2443ab0d99d..29cc9aacf0a 100644 --- a/services/api-server/src/simcore_service_api_server/models/schemas/files.py +++ b/services/api-server/src/simcore_service_api_server/models/schemas/files.py @@ -7,7 +7,7 @@ import aiofiles from fastapi import UploadFile -from models_library.api_schemas_storage.rest.storage_schemas import ETag +from models_library.api_schemas_storage.storage_schemas import ETag from models_library.basic_types import SHA256Str from models_library.projects_nodes_io import StorageFileID from pydantic import ( diff --git a/services/api-server/src/simcore_service_api_server/services_http/storage.py b/services/api-server/src/simcore_service_api_server/services_http/storage.py index 087e93e3a23..52d3c8e8ddb 100644 --- a/services/api-server/src/simcore_service_api_server/services_http/storage.py +++ b/services/api-server/src/simcore_service_api_server/services_http/storage.py @@ -8,11 +8,11 @@ from fastapi import FastAPI from fastapi.encoders import jsonable_encoder -from models_library.api_schemas_storage.rest.storage_schemas import FileMetaDataArray -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import FileMetaDataArray +from models_library.api_schemas_storage.storage_schemas import ( FileMetaDataGet as StorageFileMetaData, ) -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( FileUploadSchema, PresignedLink, ) diff --git a/services/api-server/tests/unit/conftest.py b/services/api-server/tests/unit/conftest.py index 3cf005f6ede..83db84a5d2c 100644 --- a/services/api-server/tests/unit/conftest.py +++ b/services/api-server/tests/unit/conftest.py @@ -27,7 +27,7 @@ TaskProgress, TaskStatus, ) -from models_library.api_schemas_storage.rest.storage_schemas import HealthCheck +from models_library.api_schemas_storage.storage_schemas import HealthCheck from models_library.api_schemas_webserver.projects import ProjectGet from models_library.app_diagnostics import AppStatusCheck from models_library.generics import Envelope diff --git a/services/api-server/tests/unit/test_api_files.py b/services/api-server/tests/unit/test_api_files.py index a876f0a5d11..323332f1f5b 100644 --- a/services/api-server/tests/unit/test_api_files.py +++ b/services/api-server/tests/unit/test_api_files.py @@ -18,7 +18,7 @@ from fastapi import status from fastapi.encoders import jsonable_encoder from httpx import AsyncClient -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( ETag, FileUploadCompletionBody, UploadedPart, diff --git a/services/api-server/tests/unit/test_models_schemas_files.py b/services/api-server/tests/unit/test_models_schemas_files.py index 99eab62cdac..3a57327e324 100644 --- a/services/api-server/tests/unit/test_models_schemas_files.py +++ b/services/api-server/tests/unit/test_models_schemas_files.py @@ -11,7 +11,7 @@ import pytest from fastapi import UploadFile -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( FileMetaDataGet as StorageFileMetaData, ) from models_library.basic_types import SHA256Str diff --git a/services/director-v2/tests/unit/with_dbs/test_utils_dask.py b/services/director-v2/tests/unit/with_dbs/test_utils_dask.py index 70c63c35a1f..7c991d3390d 100644 --- a/services/director-v2/tests/unit/with_dbs/test_utils_dask.py +++ b/services/director-v2/tests/unit/with_dbs/test_utils_dask.py @@ -30,7 +30,7 @@ from faker import Faker from fastapi import FastAPI from models_library.api_schemas_directorv2.services import NodeRequirements -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( FileUploadLinks, FileUploadSchema, ) diff --git a/services/dynamic-sidecar/tests/integration/test_modules_long_running_tasks.py b/services/dynamic-sidecar/tests/integration/test_modules_long_running_tasks.py index 444c7675f00..e205946c90d 100644 --- a/services/dynamic-sidecar/tests/integration/test_modules_long_running_tasks.py +++ b/services/dynamic-sidecar/tests/integration/test_modules_long_running_tasks.py @@ -19,7 +19,7 @@ from botocore.client import Config from botocore.exceptions import ClientError from fastapi import FastAPI -from models_library.api_schemas_storage.rest.storage_schemas import S3BucketName +from models_library.api_schemas_storage.storage_schemas import S3BucketName from models_library.projects import ProjectID from models_library.projects_nodes_io import NodeID, SimcoreS3FileID from models_library.users import UserID diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/service_runs_db.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/service_runs_db.py index a4a2df0a07f..ed19570b523 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/service_runs_db.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/db/service_runs_db.py @@ -9,7 +9,7 @@ from models_library.api_schemas_resource_usage_tracker.credit_transactions import ( WalletTotalCredits, ) -from models_library.api_schemas_storage.rest.storage_schemas import S3BucketName +from models_library.api_schemas_storage.storage_schemas import S3BucketName from models_library.products import ProductName from models_library.projects import ProjectID from models_library.resource_tracker import ( diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/s3.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/s3.py index aec2aa449ca..b83ce3f49db 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/s3.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/modules/s3.py @@ -3,7 +3,7 @@ from aws_library.s3 import S3NotConnectedError, SimcoreS3API from fastapi import FastAPI -from models_library.api_schemas_storage.rest.storage_schemas import S3BucketName +from models_library.api_schemas_storage.storage_schemas import S3BucketName from pydantic import TypeAdapter from servicelib.logging_utils import log_context from settings_library.s3 import S3Settings diff --git a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/service_runs.py b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/service_runs.py index 0591861dba7..9a9a1398712 100644 --- a/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/service_runs.py +++ b/services/resource-usage-tracker/src/simcore_service_resource_usage_tracker/services/service_runs.py @@ -12,7 +12,7 @@ ServiceRunGet, ServiceRunPage, ) -from models_library.api_schemas_storage.rest.storage_schemas import S3BucketName +from models_library.api_schemas_storage.storage_schemas import S3BucketName from models_library.products import ProductName from models_library.projects import ProjectID from models_library.resource_tracker import ( diff --git a/services/storage/src/simcore_service_storage/api/rest/_datasets.py b/services/storage/src/simcore_service_storage/api/rest/_datasets.py index 200fc6dc0fa..76e6e185068 100644 --- a/services/storage/src/simcore_service_storage/api/rest/_datasets.py +++ b/services/storage/src/simcore_service_storage/api/rest/_datasets.py @@ -2,7 +2,7 @@ from typing import Annotated from fastapi import APIRouter, Depends, Request -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( DatasetMetaDataGet, FileMetaDataGet, ) diff --git a/services/storage/src/simcore_service_storage/api/rest/_files.py b/services/storage/src/simcore_service_storage/api/rest/_files.py index 8c71a2395ed..3b1bb4c8a46 100644 --- a/services/storage/src/simcore_service_storage/api/rest/_files.py +++ b/services/storage/src/simcore_service_storage/api/rest/_files.py @@ -3,7 +3,7 @@ from typing import Annotated, cast from fastapi import APIRouter, Depends, Header, HTTPException, Request -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( FileMetaDataGet, FileMetaDataGetv010, FileUploadCompleteFutureResponse, diff --git a/services/storage/src/simcore_service_storage/api/rest/_health.py b/services/storage/src/simcore_service_storage/api/rest/_health.py index 16f69425db7..7272066ee75 100644 --- a/services/storage/src/simcore_service_storage/api/rest/_health.py +++ b/services/storage/src/simcore_service_storage/api/rest/_health.py @@ -8,10 +8,7 @@ from aws_library.s3 import S3AccessError from fastapi import APIRouter, Request -from models_library.api_schemas_storage.rest.storage_schemas import ( - HealthCheck, - S3BucketName, -) +from models_library.api_schemas_storage.storage_schemas import HealthCheck, S3BucketName from models_library.app_diagnostics import AppStatusCheck from models_library.generics import Envelope from pydantic import TypeAdapter diff --git a/services/storage/src/simcore_service_storage/api/rest/_locations.py b/services/storage/src/simcore_service_storage/api/rest/_locations.py index 253aaf1028b..133c65e2005 100644 --- a/services/storage/src/simcore_service_storage/api/rest/_locations.py +++ b/services/storage/src/simcore_service_storage/api/rest/_locations.py @@ -2,7 +2,7 @@ from typing import Annotated from fastapi import APIRouter, Depends, Request, status -from models_library.api_schemas_storage.rest.storage_schemas import FileLocation +from models_library.api_schemas_storage.storage_schemas import FileLocation from models_library.generics import Envelope # Exclusive for simcore-s3 storage ----------------------- diff --git a/services/storage/src/simcore_service_storage/api/rest/_simcore_s3.py b/services/storage/src/simcore_service_storage/api/rest/_simcore_s3.py index 28e489381a1..29b199e6feb 100644 --- a/services/storage/src/simcore_service_storage/api/rest/_simcore_s3.py +++ b/services/storage/src/simcore_service_storage/api/rest/_simcore_s3.py @@ -5,7 +5,7 @@ from fastapi import APIRouter, Depends, FastAPI, Request from models_library.api_schemas_long_running_tasks.base import TaskProgress from models_library.api_schemas_long_running_tasks.tasks import TaskGet -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( FileMetaDataGet, FoldersBody, ) diff --git a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py index 962a2708ff1..cee1d180c99 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py @@ -2,7 +2,7 @@ from fastapi import FastAPI from models_library.api_schemas_rpc_async_jobs.async_jobs import AsyncJobGet -from models_library.api_schemas_storage.rpc.data_export_async_jobs import ( +from models_library.api_schemas_storage.data_export_async_jobs import ( AccessRightError, DataExportError, DataExportTaskStartInput, diff --git a/services/storage/src/simcore_service_storage/constants.py b/services/storage/src/simcore_service_storage/constants.py index d613c789be2..fecbfb54e87 100644 --- a/services/storage/src/simcore_service_storage/constants.py +++ b/services/storage/src/simcore_service_storage/constants.py @@ -1,7 +1,7 @@ from typing import Final from aws_library.s3 import PRESIGNED_LINK_MAX_SIZE, S3_MAX_FILE_SIZE -from models_library.api_schemas_storage.rest.storage_schemas import LinkType +from models_library.api_schemas_storage.storage_schemas import LinkType from pydantic import ByteSize RETRY_WAIT_SECS = 2 diff --git a/services/storage/src/simcore_service_storage/datcore_dsm.py b/services/storage/src/simcore_service_storage/datcore_dsm.py index 629d4e69dee..3c9c50e3acc 100644 --- a/services/storage/src/simcore_service_storage/datcore_dsm.py +++ b/services/storage/src/simcore_service_storage/datcore_dsm.py @@ -1,7 +1,7 @@ from dataclasses import dataclass from fastapi import FastAPI -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( DatCoreDatasetName, LinkType, UploadedPart, diff --git a/services/storage/src/simcore_service_storage/dsm_factory.py b/services/storage/src/simcore_service_storage/dsm_factory.py index 5796e994165..a5d579a23ee 100644 --- a/services/storage/src/simcore_service_storage/dsm_factory.py +++ b/services/storage/src/simcore_service_storage/dsm_factory.py @@ -3,10 +3,7 @@ from dataclasses import dataclass, field from fastapi import FastAPI -from models_library.api_schemas_storage.rest.storage_schemas import ( - LinkType, - UploadedPart, -) +from models_library.api_schemas_storage.storage_schemas import LinkType, UploadedPart from models_library.basic_types import SHA256Str from models_library.projects import ProjectID from models_library.projects_nodes_io import LocationID, LocationName, StorageFileID diff --git a/services/storage/src/simcore_service_storage/models.py b/services/storage/src/simcore_service_storage/models.py index 207da2936e7..3febefb138f 100644 --- a/services/storage/src/simcore_service_storage/models.py +++ b/services/storage/src/simcore_service_storage/models.py @@ -6,7 +6,7 @@ import arrow from aws_library.s3 import UploadID -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( UNDEFINED_SIZE, UNDEFINED_SIZE_TYPE, DatasetMetaDataGet, diff --git a/services/storage/src/simcore_service_storage/modules/datcore_adapter/datcore_adapter.py b/services/storage/src/simcore_service_storage/modules/datcore_adapter/datcore_adapter.py index 8fa1330e013..ebcb56cba0c 100644 --- a/services/storage/src/simcore_service_storage/modules/datcore_adapter/datcore_adapter.py +++ b/services/storage/src/simcore_service_storage/modules/datcore_adapter/datcore_adapter.py @@ -5,7 +5,7 @@ import httpx from fastapi import FastAPI -from models_library.api_schemas_storage.rest.storage_schemas import DatCoreDatasetName +from models_library.api_schemas_storage.storage_schemas import DatCoreDatasetName from models_library.users import UserID from pydantic import AnyUrl, TypeAdapter from servicelib.fastapi.client_session import get_client_session diff --git a/services/storage/src/simcore_service_storage/simcore_s3_dsm.py b/services/storage/src/simcore_service_storage/simcore_s3_dsm.py index dfbb66a9a6d..753e36f9834 100644 --- a/services/storage/src/simcore_service_storage/simcore_s3_dsm.py +++ b/services/storage/src/simcore_service_storage/simcore_s3_dsm.py @@ -18,7 +18,7 @@ UploadID, ) from fastapi import FastAPI -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( UNDEFINED_SIZE, UNDEFINED_SIZE_TYPE, LinkType, diff --git a/services/storage/src/simcore_service_storage/utils/simcore_s3_dsm_utils.py b/services/storage/src/simcore_service_storage/utils/simcore_s3_dsm_utils.py index aaebe3a7e4d..db431b02c01 100644 --- a/services/storage/src/simcore_service_storage/utils/simcore_s3_dsm_utils.py +++ b/services/storage/src/simcore_service_storage/utils/simcore_s3_dsm_utils.py @@ -2,7 +2,7 @@ from pathlib import Path from aws_library.s3 import S3MetaData, SimcoreS3API -from models_library.api_schemas_storage.rest.storage_schemas import S3BucketName +from models_library.api_schemas_storage.storage_schemas import S3BucketName from models_library.projects_nodes_io import ( SimcoreS3DirectoryID, SimcoreS3FileID, diff --git a/services/storage/tests/conftest.py b/services/storage/tests/conftest.py index 46ccf685a89..15a95dd919a 100644 --- a/services/storage/tests/conftest.py +++ b/services/storage/tests/conftest.py @@ -23,7 +23,7 @@ from faker import Faker from fakeredis.aioredis import FakeRedis from fastapi import FastAPI -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( FileMetaDataGet, FileUploadCompleteFutureResponse, FileUploadCompleteResponse, diff --git a/services/storage/tests/unit/test_data_export.py b/services/storage/tests/unit/test_data_export.py index cebc0e85437..ac85a0cf8a7 100644 --- a/services/storage/tests/unit/test_data_export.py +++ b/services/storage/tests/unit/test_data_export.py @@ -14,7 +14,7 @@ AsyncJobStatus, ) from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE -from models_library.api_schemas_storage.rpc.data_export_async_jobs import ( +from models_library.api_schemas_storage.data_export_async_jobs import ( DataExportTaskStartInput, ) from pytest_mock import MockerFixture diff --git a/services/storage/tests/unit/test_dsm_dsmcleaner.py b/services/storage/tests/unit/test_dsm_dsmcleaner.py index 9e354075e77..6144571e3a3 100644 --- a/services/storage/tests/unit/test_dsm_dsmcleaner.py +++ b/services/storage/tests/unit/test_dsm_dsmcleaner.py @@ -16,7 +16,7 @@ import pytest from aws_library.s3 import MultiPartUploadLinks, SimcoreS3API from faker import Faker -from models_library.api_schemas_storage.rest.storage_schemas import LinkType +from models_library.api_schemas_storage.storage_schemas import LinkType from models_library.basic_types import SHA256Str from models_library.projects_nodes_io import SimcoreS3DirectoryID, SimcoreS3FileID from models_library.users import UserID diff --git a/services/storage/tests/unit/test_dsm_soft_links.py b/services/storage/tests/unit/test_dsm_soft_links.py index b67bc907dbf..da0d363482d 100644 --- a/services/storage/tests/unit/test_dsm_soft_links.py +++ b/services/storage/tests/unit/test_dsm_soft_links.py @@ -8,7 +8,7 @@ import pytest from faker import Faker -from models_library.api_schemas_storage.rest.storage_schemas import S3BucketName +from models_library.api_schemas_storage.storage_schemas import S3BucketName from models_library.projects_nodes_io import SimcoreS3FileID from models_library.users import UserID from models_library.utils.fastapi_encoders import jsonable_encoder diff --git a/services/storage/tests/unit/test_handlers_datasets.py b/services/storage/tests/unit/test_handlers_datasets.py index aa84bbe6e09..9bdbc743772 100644 --- a/services/storage/tests/unit/test_handlers_datasets.py +++ b/services/storage/tests/unit/test_handlers_datasets.py @@ -12,7 +12,7 @@ from faker import Faker from fastapi import FastAPI from httpx import AsyncClient -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( DatasetMetaDataGet, FileMetaDataGet, ) diff --git a/services/storage/tests/unit/test_handlers_files.py b/services/storage/tests/unit/test_handlers_files.py index af4ac601e0b..d6946433614 100644 --- a/services/storage/tests/unit/test_handlers_files.py +++ b/services/storage/tests/unit/test_handlers_files.py @@ -26,7 +26,7 @@ from aws_library.s3._constants import MULTIPART_UPLOADS_MIN_TOTAL_SIZE from faker import Faker from fastapi import FastAPI -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( FileMetaDataGet, FileUploadCompleteFutureResponse, FileUploadCompleteResponse, diff --git a/services/storage/tests/unit/test_handlers_files_metadata.py b/services/storage/tests/unit/test_handlers_files_metadata.py index 0881da8618e..4c70256a574 100644 --- a/services/storage/tests/unit/test_handlers_files_metadata.py +++ b/services/storage/tests/unit/test_handlers_files_metadata.py @@ -13,7 +13,7 @@ import pytest from faker import Faker from fastapi import FastAPI -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( FileMetaDataGet, SimcoreS3FileID, ) diff --git a/services/storage/tests/unit/test_handlers_health.py b/services/storage/tests/unit/test_handlers_health.py index 3f1958ab57f..640fbb376b2 100644 --- a/services/storage/tests/unit/test_handlers_health.py +++ b/services/storage/tests/unit/test_handlers_health.py @@ -7,10 +7,7 @@ import httpx import simcore_service_storage._meta from fastapi import FastAPI -from models_library.api_schemas_storage.rest.storage_schemas import ( - HealthCheck, - S3BucketName, -) +from models_library.api_schemas_storage.storage_schemas import HealthCheck, S3BucketName from models_library.app_diagnostics import AppStatusCheck from moto.server import ThreadedMotoServer from pytest_simcore.helpers.fastapi import url_from_operation_id diff --git a/services/storage/tests/unit/test_handlers_simcore_s3.py b/services/storage/tests/unit/test_handlers_simcore_s3.py index f6cbbf4b289..0ddc39f206d 100644 --- a/services/storage/tests/unit/test_handlers_simcore_s3.py +++ b/services/storage/tests/unit/test_handlers_simcore_s3.py @@ -19,7 +19,7 @@ from aws_library.s3 import SimcoreS3API from faker import Faker from fastapi import FastAPI -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( FileMetaDataGet, FoldersBody, ) diff --git a/services/storage/tests/unit/test_models.py b/services/storage/tests/unit/test_models.py index 23f238d95a9..da33d24ad50 100644 --- a/services/storage/tests/unit/test_models.py +++ b/services/storage/tests/unit/test_models.py @@ -1,7 +1,7 @@ import uuid import pytest -from models_library.api_schemas_storage.rest.storage_schemas import S3BucketName +from models_library.api_schemas_storage.storage_schemas import S3BucketName from models_library.projects import ProjectID from models_library.projects_nodes_io import NodeID, SimcoreS3FileID, StorageFileID from pydantic import TypeAdapter, ValidationError diff --git a/services/storage/tests/unit/test_simcore_s3_dsm.py b/services/storage/tests/unit/test_simcore_s3_dsm.py index 68e40d0e90a..92f3a9751bb 100644 --- a/services/storage/tests/unit/test_simcore_s3_dsm.py +++ b/services/storage/tests/unit/test_simcore_s3_dsm.py @@ -7,7 +7,7 @@ import pytest from faker import Faker -from models_library.api_schemas_storage.rest.storage_schemas import FileUploadSchema +from models_library.api_schemas_storage.storage_schemas import FileUploadSchema from models_library.basic_types import SHA256Str from models_library.projects_nodes_io import SimcoreS3FileID from models_library.users import UserID diff --git a/services/storage/tests/unit/test_utils.py b/services/storage/tests/unit/test_utils.py index 86e7076271e..3ee5d73a85a 100644 --- a/services/storage/tests/unit/test_utils.py +++ b/services/storage/tests/unit/test_utils.py @@ -13,7 +13,7 @@ import httpx import pytest from faker import Faker -from models_library.api_schemas_storage.rest.storage_schemas import UNDEFINED_SIZE_TYPE +from models_library.api_schemas_storage.storage_schemas import UNDEFINED_SIZE_TYPE from models_library.projects import ProjectID from models_library.projects_nodes_io import NodeID, SimcoreS3FileID from pydantic import ByteSize, HttpUrl, TypeAdapter diff --git a/services/web/server/src/simcore_service_webserver/projects/_nodes_api.py b/services/web/server/src/simcore_service_webserver/projects/_nodes_api.py index 4f90324ac38..6b4b8df3f90 100644 --- a/services/web/server/src/simcore_service_webserver/projects/_nodes_api.py +++ b/services/web/server/src/simcore_service_webserver/projects/_nodes_api.py @@ -7,7 +7,7 @@ from aiohttp import web from aiohttp.client import ClientError -from models_library.api_schemas_storage.rest.storage_schemas import FileMetaDataGet +from models_library.api_schemas_storage.storage_schemas import FileMetaDataGet from models_library.basic_types import KeyIDStr from models_library.projects import ProjectID from models_library.projects_nodes import Node diff --git a/services/web/server/src/simcore_service_webserver/storage/_exception_handlers.py b/services/web/server/src/simcore_service_webserver/storage/_exception_handlers.py index 1aec566f2c7..d6cecb75d9e 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_exception_handlers.py +++ b/services/web/server/src/simcore_service_webserver/storage/_exception_handlers.py @@ -1,4 +1,4 @@ -from models_library.api_schemas_storage.rpc.data_export_async_jobs import ( +from models_library.api_schemas_storage.data_export_async_jobs import ( AccessRightError, DataExportError, InvalidFileIdentifierError, diff --git a/services/web/server/src/simcore_service_webserver/storage/_rest.py b/services/web/server/src/simcore_service_webserver/storage/_rest.py index 5b1e3ed72c1..21105947e66 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_rest.py +++ b/services/web/server/src/simcore_service_webserver/storage/_rest.py @@ -10,7 +10,7 @@ from aiohttp import ClientTimeout, web from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( FileUploadCompleteResponse, FileUploadCompletionBody, FileUploadSchema, diff --git a/services/web/server/src/simcore_service_webserver/storage/api.py b/services/web/server/src/simcore_service_webserver/storage/api.py index 08efa0bc959..9d65ac3faf3 100644 --- a/services/web/server/src/simcore_service_webserver/storage/api.py +++ b/services/web/server/src/simcore_service_webserver/storage/api.py @@ -7,7 +7,7 @@ from typing import Any, Final from aiohttp import ClientError, ClientSession, ClientTimeout, web -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( FileLocation, FileLocationArray, FileMetaDataGet, diff --git a/services/web/server/tests/unit/isolated/test_projects__nodes_api.py b/services/web/server/tests/unit/isolated/test_projects__nodes_api.py index de7cc7dd8fb..27f8df86ace 100644 --- a/services/web/server/tests/unit/isolated/test_projects__nodes_api.py +++ b/services/web/server/tests/unit/isolated/test_projects__nodes_api.py @@ -2,7 +2,7 @@ from uuid import uuid4 import pytest -from models_library.api_schemas_storage.rest.storage_schemas import FileMetaDataGet +from models_library.api_schemas_storage.storage_schemas import FileMetaDataGet from simcore_service_webserver.projects._nodes_api import ( _SUPPORTED_PREVIEW_FILE_EXTENSIONS, _FileWithThumbnail, diff --git a/services/web/server/tests/unit/with_dbs/01/test_storage.py b/services/web/server/tests/unit/with_dbs/01/test_storage.py index c89a45d8ca8..02b1945f885 100644 --- a/services/web/server/tests/unit/with_dbs/01/test_storage.py +++ b/services/web/server/tests/unit/with_dbs/01/test_storage.py @@ -15,7 +15,7 @@ from aiohttp.test_utils import TestClient from faker import Faker from fastapi import APIRouter, FastAPI, Request -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( DatasetMetaDataGet, FileLocation, FileMetaDataGet, diff --git a/services/web/server/tests/unit/with_dbs/02/test_projects_nodes_handler.py b/services/web/server/tests/unit/with_dbs/02/test_projects_nodes_handler.py index b0bf1e9a727..2e7549cd7fa 100644 --- a/services/web/server/tests/unit/with_dbs/02/test_projects_nodes_handler.py +++ b/services/web/server/tests/unit/with_dbs/02/test_projects_nodes_handler.py @@ -24,7 +24,7 @@ from models_library.api_schemas_dynamic_scheduler.dynamic_services import ( DynamicServiceStop, ) -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( FileMetaDataGet, PresignedLink, ) diff --git a/services/web/server/tests/unit/with_dbs/03/test_storage_handlers.py b/services/web/server/tests/unit/with_dbs/03/test_storage_handlers.py index 660f243b7b0..b389d3fc146 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_storage_handlers.py +++ b/services/web/server/tests/unit/with_dbs/03/test_storage_handlers.py @@ -9,7 +9,7 @@ import pytest from aiohttp.test_utils import TestClient -from models_library.api_schemas_storage.rest.storage_schemas import ( +from models_library.api_schemas_storage.storage_schemas import ( FileUploadCompleteResponse, FileUploadLinks, FileUploadSchema, diff --git a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py index c69139f9bf9..7e5ab5b3edc 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py +++ b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py @@ -16,7 +16,7 @@ ResultError, StatusError, ) -from models_library.api_schemas_storage.rpc.data_export_async_jobs import ( +from models_library.api_schemas_storage.data_export_async_jobs import ( AccessRightError, DataExportError, InvalidFileIdentifierError, From 6a19215c58e3d80432ec5361d34b53f08d1d0cd3 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Feb 2025 10:57:11 +0100 Subject: [PATCH 45/63] make pylint happy --- .../web/server/tests/unit/with_dbs/03/test_storage_rpc.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py index 7e5ab5b3edc..32775b537e0 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py +++ b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py @@ -1,3 +1,5 @@ +# pylint: disable=redefined-outer-name +# pylint: disable=unused-argument from datetime import datetime from pathlib import Path from typing import Any, Callable @@ -135,7 +137,7 @@ async def test_get_async_jobs_status( elif isinstance(backend_result_or_exception, StatusError): assert response.status == status.HTTP_500_INTERNAL_SERVER_ERROR else: - raise Exception("Test incorrectly configured") + pytest.fail("Incorrectly configured test") @pytest.mark.parametrize("user_role", [UserRole.USER]) @@ -188,7 +190,7 @@ async def test_get_async_job_result( elif isinstance(backend_result_or_exception, ResultError): assert response.status == status.HTTP_500_INTERNAL_SERVER_ERROR else: - raise Exception("Test incorrectly configured") + pytest.fail("Incorrectly configured test") @pytest.mark.parametrize("user_role", [UserRole.USER]) From 8356158241f13084efb73c421e1d82804c3944b0 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Feb 2025 11:05:41 +0100 Subject: [PATCH 46/63] update openapi specs --- .../simcore_service_webserver/api/v0/openapi.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 a64e9143f26..01ea7eddc02 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 @@ -14552,13 +14552,13 @@ components: title: StatusDiagnosticsGet StorageAsyncJobGet: properties: - job_id: + jobId: type: string format: uuid - title: Job Id + title: Jobid type: object required: - - job_id + - jobId title: StorageAsyncJobGet StorageAsyncJobResult: properties: @@ -14579,10 +14579,10 @@ components: title: StorageAsyncJobResult StorageAsyncJobStatus: properties: - job_id: + jobId: type: string format: uuid - title: Job Id + title: Jobid progress: $ref: '#/components/schemas/ProgressReport' done: @@ -14600,7 +14600,7 @@ components: title: Stopped type: object required: - - job_id + - jobId - progress - done - started From da0f6eafa621bb434d9b25c12d0c411ac51dec7c Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Feb 2025 11:38:27 +0100 Subject: [PATCH 47/63] fix webserver mocks --- .../server/tests/unit/with_dbs/03/test_storage_handlers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/server/tests/unit/with_dbs/03/test_storage_handlers.py b/services/web/server/tests/unit/with_dbs/03/test_storage_handlers.py index b389d3fc146..57e16128c53 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_storage_handlers.py +++ b/services/web/server/tests/unit/with_dbs/03/test_storage_handlers.py @@ -43,7 +43,7 @@ async def _resp(*args, **kwargs) -> tuple[Any, int]: return (wrap_as_envelope(data=expected_response), 200) mocker.patch( - "simcore_service_webserver.storage._handlers._forward_request_to_storage", + "simcore_service_webserver.storage._rest._forward_request_to_storage", autospec=True, side_effect=_resp, ) @@ -52,7 +52,7 @@ def _resolve(*args, **kwargs) -> AnyUrl: return TypeAdapter(AnyUrl).validate_python("http://private-url") mocker.patch( - "simcore_service_webserver.storage._handlers._from_storage_url", + "simcore_service_webserver.storage._rest._from_storage_url", autospec=True, side_effect=_resolve, ) From 63bc45cfcf8b719a0e8e808486bcad481408a409 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Feb 2025 12:59:40 +0100 Subject: [PATCH 48/63] cleanup --- .../api_schemas_rpc_async_jobs/async_jobs.py | 7 +++++++ .../rpc_interfaces/async_jobs/async_jobs.py | 13 ++++++++++--- .../api/rpc/_async_jobs.py | 13 ++++++++++--- .../storage/tests/unit/test_data_export.py | 15 ++++++++++++--- .../simcore_service_webserver/storage/_rest.py | 18 ++++++++++++++++++ .../tests/unit/with_dbs/03/test_storage_rpc.py | 1 + 6 files changed, 58 insertions(+), 9 deletions(-) diff --git a/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py b/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py index 917b397a15e..d7621e68f44 100644 --- a/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py +++ b/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py @@ -2,6 +2,7 @@ from typing import Any, TypeAlias from uuid import UUID +from models_library.users import UserID from pydantic import BaseModel, model_validator from typing_extensions import Self @@ -41,3 +42,9 @@ class AsyncJobGet(BaseModel): class AsyncJobAbort(BaseModel): result: bool job_id: AsyncJobId + + +class AsyncJobAccessData(BaseModel): + """Data for controlling access to an async job""" + + user_id: UserID | None diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py index 807c6ed0640..d0cd4a37ef2 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py @@ -2,6 +2,7 @@ from models_library.api_schemas_rpc_async_jobs.async_jobs import ( AsyncJobAbort, + AsyncJobAccessData, AsyncJobId, AsyncJobResult, AsyncJobStatus, @@ -20,12 +21,14 @@ async def abort( rabbitmq_rpc_client: RabbitMQRPCClient, *, rpc_namespace: RPCNamespace, - job_id: AsyncJobId + job_id: AsyncJobId, + access_data: AsyncJobAccessData | None ) -> AsyncJobAbort: result = await rabbitmq_rpc_client.request( rpc_namespace, _RPC_METHOD_NAME_ADAPTER.validate_python("abort"), job_id=job_id, + access_data=access_data, timeout_s=_DEFAULT_TIMEOUT_S, ) assert isinstance(result, AsyncJobAbort) @@ -36,12 +39,14 @@ async def get_status( rabbitmq_rpc_client: RabbitMQRPCClient, *, rpc_namespace: RPCNamespace, - job_id: AsyncJobId + job_id: AsyncJobId, + access_data: AsyncJobAccessData | None ) -> AsyncJobStatus: result = await rabbitmq_rpc_client.request( rpc_namespace, _RPC_METHOD_NAME_ADAPTER.validate_python("get_status"), job_id=job_id, + access_data=access_data, timeout_s=_DEFAULT_TIMEOUT_S, ) assert isinstance(result, AsyncJobStatus) @@ -52,12 +57,14 @@ async def get_result( rabbitmq_rpc_client: RabbitMQRPCClient, *, rpc_namespace: RPCNamespace, - job_id: AsyncJobId + job_id: AsyncJobId, + access_data: AsyncJobAccessData | None ) -> AsyncJobResult: result = await rabbitmq_rpc_client.request( rpc_namespace, _RPC_METHOD_NAME_ADAPTER.validate_python("get_result"), job_id=job_id, + access_data=access_data, timeout_s=_DEFAULT_TIMEOUT_S, ) assert isinstance(result, AsyncJobResult) diff --git a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py index 6a0fff100b0..8bd8fb73a9d 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py @@ -3,6 +3,7 @@ from fastapi import FastAPI from models_library.api_schemas_rpc_async_jobs.async_jobs import ( AsyncJobAbort, + AsyncJobAccessData, AsyncJobId, AsyncJobResult, AsyncJobStatus, @@ -18,13 +19,17 @@ @router.expose() -async def abort(app: FastAPI, job_id: AsyncJobId) -> AsyncJobAbort: +async def abort( + app: FastAPI, job_id: AsyncJobId, access_data: AsyncJobAccessData | None +) -> AsyncJobAbort: assert app # nosec return AsyncJobAbort(result=True, job_id=job_id) @router.expose(reraise_if_error_type=(StatusError,)) -async def get_status(app: FastAPI, job_id: AsyncJobId) -> AsyncJobStatus: +async def get_status( + app: FastAPI, job_id: AsyncJobId, access_data: AsyncJobAccessData | None +) -> AsyncJobStatus: assert app # nosec progress_report = ProgressReport(actual_value=0.5, total=1.0, attempt=1) return AsyncJobStatus( @@ -37,7 +42,9 @@ async def get_status(app: FastAPI, job_id: AsyncJobId) -> AsyncJobStatus: @router.expose(reraise_if_error_type=(ResultError,)) -async def get_result(app: FastAPI, job_id: AsyncJobId) -> AsyncJobResult: +async def get_result( + app: FastAPI, job_id: AsyncJobId, access_data: AsyncJobAccessData | None +) -> AsyncJobResult: assert app # nosec assert job_id # nosec return AsyncJobResult(result="Here's your result.", error=None) diff --git a/services/storage/tests/unit/test_data_export.py b/services/storage/tests/unit/test_data_export.py index ac85a0cf8a7..f31202bddbd 100644 --- a/services/storage/tests/unit/test_data_export.py +++ b/services/storage/tests/unit/test_data_export.py @@ -88,7 +88,10 @@ async def test_start_data_export(rpc_client: RabbitMQRPCClient, faker: Faker): async def test_abort_data_export(rpc_client: RabbitMQRPCClient, faker: Faker): _job_id = AsyncJobId(faker.uuid4()) result = await async_jobs.abort( - rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, job_id=_job_id + rpc_client, + rpc_namespace=STORAGE_RPC_NAMESPACE, + job_id=_job_id, + access_data=None, ) assert isinstance(result, AsyncJobAbort) assert result.job_id == _job_id @@ -97,7 +100,10 @@ async def test_abort_data_export(rpc_client: RabbitMQRPCClient, faker: Faker): async def test_get_data_export_status(rpc_client: RabbitMQRPCClient, faker: Faker): _job_id = AsyncJobId(faker.uuid4()) result = await async_jobs.get_status( - rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, job_id=_job_id + rpc_client, + rpc_namespace=STORAGE_RPC_NAMESPACE, + job_id=_job_id, + access_data=None, ) assert isinstance(result, AsyncJobStatus) assert result.job_id == _job_id @@ -106,7 +112,10 @@ async def test_get_data_export_status(rpc_client: RabbitMQRPCClient, faker: Fake async def test_get_data_export_result(rpc_client: RabbitMQRPCClient, faker: Faker): _job_id = AsyncJobId(faker.uuid4()) result = await async_jobs.get_result( - rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, job_id=_job_id + rpc_client, + rpc_namespace=STORAGE_RPC_NAMESPACE, + job_id=_job_id, + access_data=None, ) assert isinstance(result, AsyncJobResult) diff --git a/services/web/server/src/simcore_service_webserver/storage/_rest.py b/services/web/server/src/simcore_service_webserver/storage/_rest.py index 21105947e66..42e8a0946d6 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_rest.py +++ b/services/web/server/src/simcore_service_webserver/storage/_rest.py @@ -9,6 +9,7 @@ from urllib.parse import quote, unquote from aiohttp import ClientTimeout, web +from models_library.api_schemas_rpc_async_jobs.async_jobs import AsyncJobAccessData from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE from models_library.api_schemas_storage.storage_schemas import ( FileUploadCompleteResponse, @@ -447,6 +448,10 @@ class _RequestContext(RequestParameters): @permission_required("storage.files.*") @handle_data_export_exceptions async def get_async_job_status(request: web.Request) -> web.Response: + class _RequestContext(RequestParameters): + user_id: UserID = Field(..., alias=RQT_USERID_KEY) # type: ignore[literal-required] + + _req_ctx = _RequestContext.model_validate(request) rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) async_job_get = parse_request_path_parameters_as(StorageAsyncJobGet, request) @@ -454,6 +459,7 @@ async def get_async_job_status(request: web.Request) -> web.Response: rabbitmq_rpc_client=rabbitmq_rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, job_id=async_job_get.job_id, + access_data=AsyncJobAccessData(user_id=_req_ctx.user_id), ) return create_data_response( StorageAsyncJobStatus.from_rpc_schema(async_job_rpc_status), @@ -469,12 +475,18 @@ async def get_async_job_status(request: web.Request) -> web.Response: @permission_required("storage.files.*") @handle_data_export_exceptions async def abort_async_job(request: web.Request) -> web.Response: + class _RequestContext(RequestParameters): + user_id: UserID = Field(..., alias=RQT_USERID_KEY) # type: ignore[literal-required] + + _req_ctx = _RequestContext.model_validate(request) + rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) async_job_get = parse_request_path_parameters_as(StorageAsyncJobGet, request) async_job_rpc_abort = await abort( rabbitmq_rpc_client=rabbitmq_rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, job_id=async_job_get.job_id, + access_data=AsyncJobAccessData(user_id=_req_ctx.user_id), ) return web.Response( status=status.HTTP_200_OK @@ -491,12 +503,18 @@ async def abort_async_job(request: web.Request) -> web.Response: @permission_required("storage.files.*") @handle_data_export_exceptions async def get_async_job_result(request: web.Request) -> web.Response: + class _RequestContext(RequestParameters): + user_id: UserID = Field(..., alias=RQT_USERID_KEY) # type: ignore[literal-required] + + _req_ctx = _RequestContext.model_validate(request) + rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) async_job_get = parse_request_path_parameters_as(StorageAsyncJobGet, request) async_job_rpc_result = await get_result( rabbitmq_rpc_client=rabbitmq_rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, job_id=async_job_get.job_id, + access_data=AsyncJobAccessData(user_id=_req_ctx.user_id), ) return create_data_response( StorageAsyncJobResult.from_rpc_schema(async_job_rpc_result), diff --git a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py index 32775b537e0..8a335f973c8 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py +++ b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py @@ -52,6 +52,7 @@ def _(method: str, result_or_exception: Any): def side_effect(*args, **kwargs): if isinstance(result_or_exception, Exception): raise result_or_exception + return result_or_exception mocker.patch( From e5ff4408a4f33c9b5c715af2963a8c57ff781ea9 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Feb 2025 13:20:57 +0100 Subject: [PATCH 49/63] make pylint happy --- .../storage/src/simcore_service_storage/api/rpc/_async_jobs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py index 8bd8fb73a9d..e25c762857a 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py @@ -1,3 +1,4 @@ +# pylint: disable=unused-argument from datetime import datetime from fastapi import FastAPI From f0848a4bc1838fa609952cab5360433fc21e7bdc Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Feb 2025 13:29:33 +0100 Subject: [PATCH 50/63] export get async jobs method in webserver openapi specs --- api/specs/web-server/_storage.py | 15 ++++++++-- .../api/v0/openapi.yaml | 30 +++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/api/specs/web-server/_storage.py b/api/specs/web-server/_storage.py index 05f8951dcda..b7ba197bf99 100644 --- a/api/specs/web-server/_storage.py +++ b/api/specs/web-server/_storage.py @@ -192,7 +192,7 @@ async def export_data(data_export: DataExportPost, location_id: LocationID): response_model=Envelope[StorageAsyncJobStatus], name="storage_async_job_status", ) -async def get_async_job_status(task_id: StorageAsyncJobGet, job_id: UUID): +async def get_async_job_status(storage_async_job_get: StorageAsyncJobGet, job_id: UUID): """Get async job status""" @@ -200,7 +200,7 @@ async def get_async_job_status(task_id: StorageAsyncJobGet, job_id: UUID): "/storage/async-jobs/{job_id}:abort", name="abort_async_job", ) -async def abort_async_job(task_id: StorageAsyncJobGet, job_id: UUID): +async def abort_async_job(storage_async_job_get: StorageAsyncJobGet, job_id: UUID): """Get async job status""" @@ -209,5 +209,14 @@ async def abort_async_job(task_id: StorageAsyncJobGet, job_id: UUID): response_model=Envelope[StorageAsyncJobResult], name="get_async_job_result", ) -async def get_async_job_result(task_id: StorageAsyncJobGet, job_id: UUID): +async def get_async_job_result(storage_async_job_get: StorageAsyncJobGet, job_id: UUID): + """Get async job status""" + + +@router.get( + "/storage/async-jobs", + response_model=Envelope[list[StorageAsyncJobGet]], + name="get_async_job_result", +) +async def get_async_jobs(): """Get async job status""" 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 01ea7eddc02..134e9c8351a 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 @@ -6461,6 +6461,20 @@ paths: application/json: schema: $ref: '#/components/schemas/Envelope_StorageAsyncJobResult_' + /v0/storage/async-jobs: + get: + tags: + - storage + summary: Get Async Job Result + description: Get async job status + operationId: get_async_jobs + responses: + '200': + description: Successful Response + content: + application/json: + schema: + $ref: '#/components/schemas/Envelope_list_StorageAsyncJobGet__' /v0/trash:empty: post: tags: @@ -9747,6 +9761,22 @@ components: title: Error type: object title: Envelope[list[ServiceOutputGet]] + Envelope_list_StorageAsyncJobGet__: + properties: + data: + anyOf: + - items: + $ref: '#/components/schemas/StorageAsyncJobGet' + type: array + - type: 'null' + title: Data + error: + anyOf: + - {} + - type: 'null' + title: Error + type: object + title: Envelope[list[StorageAsyncJobGet]] Envelope_list_TagGet__: properties: data: From 98da41b264c0272663bd356c31b538de63f1c47b Mon Sep 17 00:00:00 2001 From: Mads Bisgaard <126242332+bisgaard-itis@users.noreply.github.com> Date: Wed, 19 Feb 2025 20:15:27 +0100 Subject: [PATCH 51/63] Update api/specs/web-server/_storage.py Co-authored-by: Andrei Neagu <5694077+GitHK@users.noreply.github.com> --- api/specs/web-server/_storage.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/specs/web-server/_storage.py b/api/specs/web-server/_storage.py index b7ba197bf99..25b3b7da842 100644 --- a/api/specs/web-server/_storage.py +++ b/api/specs/web-server/_storage.py @@ -216,7 +216,7 @@ async def get_async_job_result(storage_async_job_get: StorageAsyncJobGet, job_id @router.get( "/storage/async-jobs", response_model=Envelope[list[StorageAsyncJobGet]], - name="get_async_job_result", + name="get_async_jobs", ) -async def get_async_jobs(): - """Get async job status""" +async def get_async_jobs(user_id: UserID): + """Retrunsa list of async jobs for the user""" From 83a89418bb499290fd599404320192e3987cfdd6 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard <126242332+bisgaard-itis@users.noreply.github.com> Date: Wed, 19 Feb 2025 20:16:01 +0100 Subject: [PATCH 52/63] Update api/specs/web-server/_storage.py Co-authored-by: Andrei Neagu <5694077+GitHK@users.noreply.github.com> --- api/specs/web-server/_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/specs/web-server/_storage.py b/api/specs/web-server/_storage.py index 25b3b7da842..415e0266160 100644 --- a/api/specs/web-server/_storage.py +++ b/api/specs/web-server/_storage.py @@ -210,7 +210,7 @@ async def abort_async_job(storage_async_job_get: StorageAsyncJobGet, job_id: UUI name="get_async_job_result", ) async def get_async_job_result(storage_async_job_get: StorageAsyncJobGet, job_id: UUID): - """Get async job status""" + """Get the result of the async job""" @router.get( From bc49fb0ae6a6ebe8667d8ff003ebd12b5655b134 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard <126242332+bisgaard-itis@users.noreply.github.com> Date: Wed, 19 Feb 2025 20:16:12 +0100 Subject: [PATCH 53/63] Update api/specs/web-server/_storage.py Co-authored-by: Andrei Neagu <5694077+GitHK@users.noreply.github.com> --- api/specs/web-server/_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/specs/web-server/_storage.py b/api/specs/web-server/_storage.py index 415e0266160..68cda788b5f 100644 --- a/api/specs/web-server/_storage.py +++ b/api/specs/web-server/_storage.py @@ -201,7 +201,7 @@ async def get_async_job_status(storage_async_job_get: StorageAsyncJobGet, job_id name="abort_async_job", ) async def abort_async_job(storage_async_job_get: StorageAsyncJobGet, job_id: UUID): - """Get async job status""" + """aborts execution of an async job""" @router.get( From e1488a8c4258727d2c1a0a66c69eea21f713bf87 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard <126242332+bisgaard-itis@users.noreply.github.com> Date: Wed, 19 Feb 2025 20:16:28 +0100 Subject: [PATCH 54/63] Update api/specs/web-server/_storage.py Co-authored-by: Andrei Neagu <5694077+GitHK@users.noreply.github.com> --- api/specs/web-server/_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/specs/web-server/_storage.py b/api/specs/web-server/_storage.py index 68cda788b5f..29443656d51 100644 --- a/api/specs/web-server/_storage.py +++ b/api/specs/web-server/_storage.py @@ -190,7 +190,7 @@ async def export_data(data_export: DataExportPost, location_id: LocationID): @router.get( "/storage/async-jobs/{job_id}/status", response_model=Envelope[StorageAsyncJobStatus], - name="storage_async_job_status", + name="get_async_job_status", ) async def get_async_job_status(storage_async_job_get: StorageAsyncJobGet, job_id: UUID): """Get async job status""" From 247d64fa60175a2c3aa49d2bb3544afee8ca2427 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard <126242332+bisgaard-itis@users.noreply.github.com> Date: Wed, 19 Feb 2025 20:22:40 +0100 Subject: [PATCH 55/63] Update api/specs/web-server/_storage.py Co-authored-by: Andrei Neagu <5694077+GitHK@users.noreply.github.com> --- api/specs/web-server/_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/specs/web-server/_storage.py b/api/specs/web-server/_storage.py index 29443656d51..7127c79eb1d 100644 --- a/api/specs/web-server/_storage.py +++ b/api/specs/web-server/_storage.py @@ -180,7 +180,7 @@ async def is_completed_upload_file( @router.post( "/storage/locations/{location_id}/export-data", response_model=Envelope[StorageAsyncJobGet], - name="storage_export_data", + name="export_data", description="Export data", ) async def export_data(data_export: DataExportPost, location_id: LocationID): From 61d0683fe3b3aa131001a62c0b44dc957b4c3e48 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Feb 2025 20:52:55 +0100 Subject: [PATCH 56/63] use AsyncJobId consistently @GitHK --- .../src/simcore_service_storage/api/rpc/_data_export.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py index cee1d180c99..2f3ad030a7e 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py @@ -1,7 +1,7 @@ from uuid import uuid4 from fastapi import FastAPI -from models_library.api_schemas_rpc_async_jobs.async_jobs import AsyncJobGet +from models_library.api_schemas_rpc_async_jobs.async_jobs import AsyncJobGet, AsyncJobId from models_library.api_schemas_storage.data_export_async_jobs import ( AccessRightError, DataExportError, @@ -26,7 +26,7 @@ async def start_data_export( ) -> AsyncJobGet: assert app # nosec return AsyncJobGet( - job_id=uuid4(), + job_id=AsyncJobId(uuid4()), job_name=", ".join(str(p) for p in paths.paths), ) @@ -35,4 +35,4 @@ async def start_data_export( async def get_user_jobs(app: FastAPI, user_id: UserID) -> list[AsyncJobGet]: assert app # nosec assert user_id # nosec - return [AsyncJobGet(job_id=uuid4(), job_name="myjob")] + return [AsyncJobGet(job_id=AsyncJobId(uuid4()), job_name="myjob")] From 6132f82ba93a01faabbc7569bf048e35eb0ec16f Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Feb 2025 20:58:06 +0100 Subject: [PATCH 57/63] update openapi specs --- api/specs/web-server/_storage.py | 1 + .../api/v0/openapi.yaml | 21 +++++++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/api/specs/web-server/_storage.py b/api/specs/web-server/_storage.py index 7127c79eb1d..50e1eaeb5fd 100644 --- a/api/specs/web-server/_storage.py +++ b/api/specs/web-server/_storage.py @@ -26,6 +26,7 @@ ) from models_library.generics import Envelope from models_library.projects_nodes_io import LocationID +from models_library.users import UserID from pydantic import AnyUrl, ByteSize from simcore_service_webserver._meta import API_VTAG from simcore_service_webserver.storage.schemas import DatasetMetaData, FileMetaData 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 134e9c8351a..1e6ca222ff4 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 @@ -6355,7 +6355,7 @@ paths: post: tags: - storage - summary: Storage Export Data + summary: Export Data description: Export data operationId: export_data parameters: @@ -6382,7 +6382,7 @@ paths: get: tags: - storage - summary: Storage Async Job Status + summary: Get Async Job Status description: Get async job status operationId: get_async_job_status parameters: @@ -6411,7 +6411,7 @@ paths: tags: - storage summary: Abort Async Job - description: Get async job status + description: aborts execution of an async job operationId: abort_async_job parameters: - name: job_id @@ -6438,7 +6438,7 @@ paths: tags: - storage summary: Get Async Job Result - description: Get async job status + description: Get the result of the async job operationId: get_async_job_result parameters: - name: job_id @@ -6465,9 +6465,18 @@ paths: get: tags: - storage - summary: Get Async Job Result - description: Get async job status + summary: Get Async Jobs + description: Retrunsa list of async jobs for the user operationId: get_async_jobs + parameters: + - name: user_id + in: query + required: true + schema: + type: integer + exclusiveMinimum: true + title: User Id + minimum: 0 responses: '200': description: Successful Response From e50c1aa76abe9b6933a5dbab5a6dd36b75fd5d42 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Feb 2025 21:31:08 +0100 Subject: [PATCH 58/63] make list jobs endpoint generic --- .../rpc_interfaces/async_jobs/async_jobs.py | 13 +++++++++++++ .../rabbitmq/rpc_interfaces/storage/data_export.py | 13 ------------- .../simcore_service_storage/api/rpc/_async_jobs.py | 8 ++++++++ .../simcore_service_storage/api/rpc/_data_export.py | 10 +--------- services/storage/tests/unit/test_data_export.py | 6 +++--- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py index d0cd4a37ef2..a01def6ba79 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py @@ -3,6 +3,7 @@ from models_library.api_schemas_rpc_async_jobs.async_jobs import ( AsyncJobAbort, AsyncJobAccessData, + AsyncJobGet, AsyncJobId, AsyncJobResult, AsyncJobStatus, @@ -69,3 +70,15 @@ async def get_result( ) assert isinstance(result, AsyncJobResult) return result + + +async def list_jobs( + rabbitmq_rpc_client: RabbitMQRPCClient, *, rpc_namespace: RPCNamespace, filter: str +) -> list[AsyncJobGet]: + result: list[AsyncJobGet] = await rabbitmq_rpc_client.request( + rpc_namespace, + _RPC_METHOD_NAME_ADAPTER.validate_python("list_jobs"), + filter=str, + timeout_s=_DEFAULT_TIMEOUT_S, + ) + return result diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py index ceae1e57195..1b773fbdec9 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py @@ -6,7 +6,6 @@ DataExportTaskStartInput, ) from models_library.rabbitmq_basic_types import RPCMethodName -from models_library.users import UserID from pydantic import NonNegativeInt, TypeAdapter from ... import RabbitMQRPCClient @@ -27,15 +26,3 @@ async def start_data_export( ) assert isinstance(result, AsyncJobGet) return result - - -async def get_user_jobs( - rabbitmq_rpc_client: RabbitMQRPCClient, *, user_id: UserID -) -> list[AsyncJobGet]: - result: list[AsyncJobGet] = await rabbitmq_rpc_client.request( - STORAGE_RPC_NAMESPACE, - _RPC_METHOD_NAME_ADAPTER.validate_python("get_user_jobs"), - user_id=user_id, - timeout_s=_DEFAULT_TIMEOUT_S, - ) - return result diff --git a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py index e25c762857a..333ea0c6983 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py @@ -1,10 +1,12 @@ # pylint: disable=unused-argument from datetime import datetime +from uuid import uuid4 from fastapi import FastAPI from models_library.api_schemas_rpc_async_jobs.async_jobs import ( AsyncJobAbort, AsyncJobAccessData, + AsyncJobGet, AsyncJobId, AsyncJobResult, AsyncJobStatus, @@ -49,3 +51,9 @@ async def get_result( assert app # nosec assert job_id # nosec return AsyncJobResult(result="Here's your result.", error=None) + + +@router.expose() +async def list_jobs(app: FastAPI, filter: str) -> list[AsyncJobGet]: + assert app # nosec + return [AsyncJobGet(job_id=AsyncJobId(f"{uuid4()}"), job_name="myjob")] diff --git a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py index 2f3ad030a7e..644daf24586 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_data_export.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_data_export.py @@ -8,7 +8,6 @@ DataExportTaskStartInput, InvalidFileIdentifierError, ) -from models_library.users import UserID from servicelib.rabbitmq import RPCRouter router = RPCRouter() @@ -26,13 +25,6 @@ async def start_data_export( ) -> AsyncJobGet: assert app # nosec return AsyncJobGet( - job_id=AsyncJobId(uuid4()), + job_id=AsyncJobId(f"{uuid4()}"), job_name=", ".join(str(p) for p in paths.paths), ) - - -@router.expose() -async def get_user_jobs(app: FastAPI, user_id: UserID) -> list[AsyncJobGet]: - assert app # nosec - assert user_id # nosec - return [AsyncJobGet(job_id=AsyncJobId(uuid4()), job_name="myjob")] diff --git a/services/storage/tests/unit/test_data_export.py b/services/storage/tests/unit/test_data_export.py index f31202bddbd..731d753140b 100644 --- a/services/storage/tests/unit/test_data_export.py +++ b/services/storage/tests/unit/test_data_export.py @@ -120,9 +120,9 @@ async def test_get_data_export_result(rpc_client: RabbitMQRPCClient, faker: Fake assert isinstance(result, AsyncJobResult) -async def test_get_user_jobs(rpc_client: RabbitMQRPCClient, faker: Faker): - result = await data_export.get_user_jobs( - rpc_client, user_id=faker.pyint(min_value=1) +async def test_list_jobs(rpc_client: RabbitMQRPCClient, faker: Faker): + result = await async_jobs.list_jobs( + rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, filter="" ) assert isinstance(result, list) assert all(isinstance(elm, AsyncJobGet) for elm in result) From 57e2576020361772e12dd61350e0a0936b392b4c Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Feb 2025 21:40:02 +0100 Subject: [PATCH 59/63] propagate changes to webserver --- .../src/simcore_service_webserver/storage/_rest.py | 13 +++++++------ .../tests/unit/with_dbs/03/test_storage_rpc.py | 8 +++----- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/services/web/server/src/simcore_service_webserver/storage/_rest.py b/services/web/server/src/simcore_service_webserver/storage/_rest.py index 42e8a0946d6..4744a46e807 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_rest.py +++ b/services/web/server/src/simcore_service_webserver/storage/_rest.py @@ -3,6 +3,7 @@ Mostly resolves and redirect to storage API """ +import json import logging import urllib.parse from typing import Any, Final, NamedTuple @@ -41,11 +42,9 @@ abort, get_result, get_status, + list_jobs, ) -from servicelib.rabbitmq.rpc_interfaces.storage.data_export import ( - get_user_jobs, - start_data_export, -) +from servicelib.rabbitmq.rpc_interfaces.storage.data_export import start_data_export from servicelib.request_keys import RQT_USERID_KEY from servicelib.rest_responses import unwrap_envelope from simcore_service_webserver.rabbitmq import get_rabbitmq_rpc_client @@ -431,8 +430,10 @@ class _RequestContext(RequestParameters): rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) - user_async_jobs = await get_user_jobs( - rabbitmq_rpc_client=rabbitmq_rpc_client, user_id=_req_ctx.user_id + user_async_jobs = await list_jobs( + rabbitmq_rpc_client=rabbitmq_rpc_client, + rpc_namespace=STORAGE_RPC_NAMESPACE, + filter=json.dumps({"user_id": _req_ctx.user_id}), ) return create_data_response( [StorageAsyncJobGet.from_rpc_schema(job) for job in user_async_jobs], diff --git a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py index 8a335f973c8..6ecf33edaa9 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py +++ b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py @@ -36,11 +36,9 @@ abort, get_result, get_status, + list_jobs, ) -from servicelib.rabbitmq.rpc_interfaces.storage.data_export import ( - get_user_jobs, - start_data_export, -) +from servicelib.rabbitmq.rpc_interfaces.storage.data_export import start_data_export from simcore_postgres_database.models.users import UserRole _faker = Faker() @@ -202,7 +200,7 @@ async def test_get_user_async_jobs( create_storage_rpc_client_mock: Callable[[str, Any], None], ): create_storage_rpc_client_mock( - get_user_jobs.__name__, [StorageAsyncJobGet(job_id=AsyncJobId(_faker.uuid4()))] + list_jobs.__name__, [StorageAsyncJobGet(job_id=AsyncJobId(_faker.uuid4()))] ) response = await client.get("/v0/storage/async-jobs") From 25265cb23531709ba3baacef719450074c9ab899 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Wed, 19 Feb 2025 21:56:43 +0100 Subject: [PATCH 60/63] make pylint happy --- .../rabbitmq/rpc_interfaces/async_jobs/async_jobs.py | 4 ++-- .../src/simcore_service_storage/api/rpc/_async_jobs.py | 2 +- services/storage/tests/unit/test_data_export.py | 2 +- .../web/server/src/simcore_service_webserver/storage/_rest.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py index a01def6ba79..3717ee654d5 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py @@ -73,12 +73,12 @@ async def get_result( async def list_jobs( - rabbitmq_rpc_client: RabbitMQRPCClient, *, rpc_namespace: RPCNamespace, filter: str + rabbitmq_rpc_client: RabbitMQRPCClient, *, rpc_namespace: RPCNamespace, filter_: str ) -> list[AsyncJobGet]: result: list[AsyncJobGet] = await rabbitmq_rpc_client.request( rpc_namespace, _RPC_METHOD_NAME_ADAPTER.validate_python("list_jobs"), - filter=str, + filter_=filter_, timeout_s=_DEFAULT_TIMEOUT_S, ) return result diff --git a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py index 333ea0c6983..c9e9699942a 100644 --- a/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py +++ b/services/storage/src/simcore_service_storage/api/rpc/_async_jobs.py @@ -54,6 +54,6 @@ async def get_result( @router.expose() -async def list_jobs(app: FastAPI, filter: str) -> list[AsyncJobGet]: +async def list_jobs(app: FastAPI, filter_: str) -> list[AsyncJobGet]: assert app # nosec return [AsyncJobGet(job_id=AsyncJobId(f"{uuid4()}"), job_name="myjob")] diff --git a/services/storage/tests/unit/test_data_export.py b/services/storage/tests/unit/test_data_export.py index 731d753140b..95ec2b1bb4c 100644 --- a/services/storage/tests/unit/test_data_export.py +++ b/services/storage/tests/unit/test_data_export.py @@ -122,7 +122,7 @@ async def test_get_data_export_result(rpc_client: RabbitMQRPCClient, faker: Fake async def test_list_jobs(rpc_client: RabbitMQRPCClient, faker: Faker): result = await async_jobs.list_jobs( - rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, filter="" + rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, filter_="" ) assert isinstance(result, list) assert all(isinstance(elm, AsyncJobGet) for elm in result) diff --git a/services/web/server/src/simcore_service_webserver/storage/_rest.py b/services/web/server/src/simcore_service_webserver/storage/_rest.py index 4744a46e807..7a614896908 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_rest.py +++ b/services/web/server/src/simcore_service_webserver/storage/_rest.py @@ -433,7 +433,7 @@ class _RequestContext(RequestParameters): user_async_jobs = await list_jobs( rabbitmq_rpc_client=rabbitmq_rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, - filter=json.dumps({"user_id": _req_ctx.user_id}), + filter_=json.dumps({"user_id": _req_ctx.user_id}), ) return create_data_response( [StorageAsyncJobGet.from_rpc_schema(job) for job in user_async_jobs], From dd3fdf9f4260d7bb93f97587b65d633e37842600 Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Thu, 20 Feb 2025 10:12:39 +0100 Subject: [PATCH 61/63] make submit job endpoint generic @GitHK --- .../rpc_interfaces/async_jobs/async_jobs.py | 17 +++++++++++ .../rpc_interfaces/storage/__init__.py | 0 .../rpc_interfaces/storage/data_export.py | 28 ------------------- .../storage/tests/unit/test_data_export.py | 5 ++-- .../storage/_rest.py | 6 ++-- 5 files changed, 24 insertions(+), 32 deletions(-) delete mode 100644 packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/__init__.py delete mode 100644 packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py index 3717ee654d5..8daa9f674c4 100644 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py +++ b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/async_jobs/async_jobs.py @@ -82,3 +82,20 @@ async def list_jobs( timeout_s=_DEFAULT_TIMEOUT_S, ) return result + + +async def submit_job( + rabbitmq_rpc_client: RabbitMQRPCClient, + *, + rpc_namespace: RPCNamespace, + job_name: str, + **kwargs +) -> AsyncJobGet: + result = await rabbitmq_rpc_client.request( + rpc_namespace, + _RPC_METHOD_NAME_ADAPTER.validate_python(job_name), + **kwargs, + timeout_s=_DEFAULT_TIMEOUT_S, + ) + assert isinstance(result, AsyncJobGet) + return result diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/__init__.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py b/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py deleted file mode 100644 index 1b773fbdec9..00000000000 --- a/packages/service-library/src/servicelib/rabbitmq/rpc_interfaces/storage/data_export.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import Final - -from models_library.api_schemas_rpc_async_jobs.async_jobs import AsyncJobGet -from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE -from models_library.api_schemas_storage.data_export_async_jobs import ( - DataExportTaskStartInput, -) -from models_library.rabbitmq_basic_types import RPCMethodName -from pydantic import NonNegativeInt, TypeAdapter - -from ... import RabbitMQRPCClient - -_DEFAULT_TIMEOUT_S: Final[NonNegativeInt] = 30 - -_RPC_METHOD_NAME_ADAPTER = TypeAdapter(RPCMethodName) - - -async def start_data_export( - rabbitmq_rpc_client: RabbitMQRPCClient, *, paths: DataExportTaskStartInput -) -> AsyncJobGet: - result = await rabbitmq_rpc_client.request( - STORAGE_RPC_NAMESPACE, - _RPC_METHOD_NAME_ADAPTER.validate_python("start_data_export"), - paths=paths, - timeout_s=_DEFAULT_TIMEOUT_S, - ) - assert isinstance(result, AsyncJobGet) - return result diff --git a/services/storage/tests/unit/test_data_export.py b/services/storage/tests/unit/test_data_export.py index 95ec2b1bb4c..701492a86ae 100644 --- a/services/storage/tests/unit/test_data_export.py +++ b/services/storage/tests/unit/test_data_export.py @@ -22,7 +22,6 @@ from pytest_simcore.helpers.typing_env import EnvVarsDict from servicelib.rabbitmq import RabbitMQRPCClient from servicelib.rabbitmq.rpc_interfaces.async_jobs import async_jobs -from servicelib.rabbitmq.rpc_interfaces.storage import data_export from settings_library.rabbit import RabbitSettings from simcore_service_storage.core.settings import ApplicationSettings @@ -76,8 +75,10 @@ async def rpc_client( async def test_start_data_export(rpc_client: RabbitMQRPCClient, faker: Faker): - result = await data_export.start_data_export( + result = await async_jobs.submit_job( rpc_client, + rpc_namespace=STORAGE_RPC_NAMESPACE, + job_name="start_data_export", paths=DataExportTaskStartInput( user_id=1, location_id=0, paths=[Path(faker.file_path())] ), diff --git a/services/web/server/src/simcore_service_webserver/storage/_rest.py b/services/web/server/src/simcore_service_webserver/storage/_rest.py index 7a614896908..2d8ce3fcc7d 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_rest.py +++ b/services/web/server/src/simcore_service_webserver/storage/_rest.py @@ -43,8 +43,8 @@ get_result, get_status, list_jobs, + submit_job, ) -from servicelib.rabbitmq.rpc_interfaces.storage.data_export import start_data_export from servicelib.request_keys import RQT_USERID_KEY from servicelib.rest_responses import unwrap_envelope from simcore_service_webserver.rabbitmq import get_rabbitmq_rpc_client @@ -403,8 +403,10 @@ class _PathParams(BaseModel): data_export_post = await parse_request_body_as( model_schema_cls=DataExportPost, request=request ) - async_job_rpc_get = await start_data_export( + async_job_rpc_get = await submit_job( rabbitmq_rpc_client=rabbitmq_rpc_client, + rpc_namespace=STORAGE_RPC_NAMESPACE, + job_name="start_data_export", paths=data_export_post.to_rpc_schema( user_id=_req_ctx.user_id, location_id=_path_params.location_id ), From fe8f33e8ff9ceddea0721aa2e81d3089b32e763b Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Thu, 20 Feb 2025 10:25:21 +0100 Subject: [PATCH 62/63] factor out RequestContext and forward product name to storage @matusdrobuliak66 --- .../api_schemas_rpc_async_jobs/async_jobs.py | 1 + .../data_export_async_jobs.py | 1 + .../api_schemas_webserver/storage.py | 7 ++- .../storage/_rest.py | 47 +++++++++---------- .../unit/with_dbs/03/test_storage_rpc.py | 4 +- 5 files changed, 31 insertions(+), 29 deletions(-) diff --git a/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py b/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py index d7621e68f44..560c4063250 100644 --- a/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py +++ b/packages/models-library/src/models_library/api_schemas_rpc_async_jobs/async_jobs.py @@ -48,3 +48,4 @@ class AsyncJobAccessData(BaseModel): """Data for controlling access to an async job""" user_id: UserID | None + product_name: str diff --git a/packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py b/packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py index 528f40b78e1..3645c918e99 100644 --- a/packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py +++ b/packages/models-library/src/models_library/api_schemas_storage/data_export_async_jobs.py @@ -9,6 +9,7 @@ class DataExportTaskStartInput(BaseModel): user_id: UserID + product_name: str location_id: LocationID paths: list[Path] = Field(..., min_length=1) diff --git a/packages/models-library/src/models_library/api_schemas_webserver/storage.py b/packages/models-library/src/models_library/api_schemas_webserver/storage.py index d623396a6a3..10808b69049 100644 --- a/packages/models-library/src/models_library/api_schemas_webserver/storage.py +++ b/packages/models-library/src/models_library/api_schemas_webserver/storage.py @@ -19,10 +19,13 @@ class DataExportPost(InputSchema): paths: list[Path] def to_rpc_schema( - self, user_id: UserID, location_id: LocationID + self, user_id: UserID, product_name: str, location_id: LocationID ) -> DataExportTaskStartInput: return DataExportTaskStartInput( - paths=self.paths, user_id=user_id, location_id=location_id + paths=self.paths, + user_id=user_id, + product_name=product_name, + location_id=location_id, ) diff --git a/services/web/server/src/simcore_service_webserver/storage/_rest.py b/services/web/server/src/simcore_service_webserver/storage/_rest.py index 2d8ce3fcc7d..5dc1fb227fd 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_rest.py +++ b/services/web/server/src/simcore_service_webserver/storage/_rest.py @@ -25,10 +25,8 @@ StorageAsyncJobStatus, ) from models_library.projects_nodes_io import LocationID -from models_library.rest_base import RequestParameters -from models_library.users import UserID from models_library.utils.fastapi_encoders import jsonable_encoder -from pydantic import AnyUrl, BaseModel, ByteSize, Field, TypeAdapter +from pydantic import AnyUrl, BaseModel, ByteSize, TypeAdapter from servicelib.aiohttp import status from servicelib.aiohttp.client_session import get_client_session from servicelib.aiohttp.requests_validation import ( @@ -52,6 +50,7 @@ from .._meta import API_VTAG from ..login.decorators import login_required +from ..models import RequestContext from ..security.decorators import permission_required from ._exception_handlers import handle_data_export_exceptions from .schemas import StorageFileIDStr @@ -391,14 +390,11 @@ class _PathParams(BaseModel): @permission_required("storage.files.*") @handle_data_export_exceptions async def export_data(request: web.Request) -> web.Response: - class _RequestContext(RequestParameters): - user_id: UserID = Field(..., alias=RQT_USERID_KEY) # type: ignore[literal-required] - class _PathParams(BaseModel): location_id: LocationID rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) - _req_ctx = _RequestContext.model_validate(request) + _req_ctx = RequestContext.model_validate(request) _path_params = parse_request_path_parameters_as(_PathParams, request) data_export_post = await parse_request_body_as( model_schema_cls=DataExportPost, request=request @@ -408,7 +404,9 @@ class _PathParams(BaseModel): rpc_namespace=STORAGE_RPC_NAMESPACE, job_name="start_data_export", paths=data_export_post.to_rpc_schema( - user_id=_req_ctx.user_id, location_id=_path_params.location_id + user_id=_req_ctx.user_id, + product_name=_req_ctx.product_name, + location_id=_path_params.location_id, ), ) return create_data_response( @@ -425,17 +423,17 @@ class _PathParams(BaseModel): @permission_required("storage.files.*") @handle_data_export_exceptions async def get_async_jobs(request: web.Request) -> web.Response: - class _RequestContext(RequestParameters): - user_id: UserID = Field(..., alias=RQT_USERID_KEY) # type: ignore[literal-required] - _req_ctx = _RequestContext.model_validate(request) + _req_ctx = RequestContext.model_validate(request) rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) user_async_jobs = await list_jobs( rabbitmq_rpc_client=rabbitmq_rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, - filter_=json.dumps({"user_id": _req_ctx.user_id}), + filter_=json.dumps( + {"user_id": _req_ctx.user_id, "product_name": _req_ctx.product_name} + ), ) return create_data_response( [StorageAsyncJobGet.from_rpc_schema(job) for job in user_async_jobs], @@ -451,10 +449,8 @@ class _RequestContext(RequestParameters): @permission_required("storage.files.*") @handle_data_export_exceptions async def get_async_job_status(request: web.Request) -> web.Response: - class _RequestContext(RequestParameters): - user_id: UserID = Field(..., alias=RQT_USERID_KEY) # type: ignore[literal-required] - _req_ctx = _RequestContext.model_validate(request) + _req_ctx = RequestContext.model_validate(request) rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) async_job_get = parse_request_path_parameters_as(StorageAsyncJobGet, request) @@ -462,7 +458,9 @@ class _RequestContext(RequestParameters): rabbitmq_rpc_client=rabbitmq_rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, job_id=async_job_get.job_id, - access_data=AsyncJobAccessData(user_id=_req_ctx.user_id), + access_data=AsyncJobAccessData( + user_id=_req_ctx.user_id, product_name=_req_ctx.product_name + ), ) return create_data_response( StorageAsyncJobStatus.from_rpc_schema(async_job_rpc_status), @@ -478,10 +476,7 @@ class _RequestContext(RequestParameters): @permission_required("storage.files.*") @handle_data_export_exceptions async def abort_async_job(request: web.Request) -> web.Response: - class _RequestContext(RequestParameters): - user_id: UserID = Field(..., alias=RQT_USERID_KEY) # type: ignore[literal-required] - - _req_ctx = _RequestContext.model_validate(request) + _req_ctx = RequestContext.model_validate(request) rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) async_job_get = parse_request_path_parameters_as(StorageAsyncJobGet, request) @@ -489,7 +484,9 @@ class _RequestContext(RequestParameters): rabbitmq_rpc_client=rabbitmq_rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, job_id=async_job_get.job_id, - access_data=AsyncJobAccessData(user_id=_req_ctx.user_id), + access_data=AsyncJobAccessData( + user_id=_req_ctx.user_id, product_name=_req_ctx.product_name + ), ) return web.Response( status=status.HTTP_200_OK @@ -506,10 +503,8 @@ class _RequestContext(RequestParameters): @permission_required("storage.files.*") @handle_data_export_exceptions async def get_async_job_result(request: web.Request) -> web.Response: - class _RequestContext(RequestParameters): - user_id: UserID = Field(..., alias=RQT_USERID_KEY) # type: ignore[literal-required] - _req_ctx = _RequestContext.model_validate(request) + _req_ctx = RequestContext.model_validate(request) rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) async_job_get = parse_request_path_parameters_as(StorageAsyncJobGet, request) @@ -517,7 +512,9 @@ class _RequestContext(RequestParameters): rabbitmq_rpc_client=rabbitmq_rpc_client, rpc_namespace=STORAGE_RPC_NAMESPACE, job_id=async_job_get.job_id, - access_data=AsyncJobAccessData(user_id=_req_ctx.user_id), + access_data=AsyncJobAccessData( + user_id=_req_ctx.user_id, product_name=_req_ctx.product_name + ), ) return create_data_response( StorageAsyncJobResult.from_rpc_schema(async_job_rpc_result), diff --git a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py index 6ecf33edaa9..e68f3ad1ef9 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py +++ b/services/web/server/tests/unit/with_dbs/03/test_storage_rpc.py @@ -37,8 +37,8 @@ get_result, get_status, list_jobs, + submit_job, ) -from servicelib.rabbitmq.rpc_interfaces.storage.data_export import start_data_export from simcore_postgres_database.models.users import UserRole _faker = Faker() @@ -81,7 +81,7 @@ async def test_data_export( backend_result_or_exception: Any, ): create_storage_rpc_client_mock( - start_data_export.__name__, + submit_job.__name__, backend_result_or_exception, ) From 38030ab9e895792709435ecf06519dc9cc47c45e Mon Sep 17 00:00:00 2001 From: Mads Bisgaard Date: Thu, 20 Feb 2025 10:42:34 +0100 Subject: [PATCH 63/63] fix test in storage --- services/storage/tests/unit/test_data_export.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/storage/tests/unit/test_data_export.py b/services/storage/tests/unit/test_data_export.py index 701492a86ae..bb25413e948 100644 --- a/services/storage/tests/unit/test_data_export.py +++ b/services/storage/tests/unit/test_data_export.py @@ -80,7 +80,10 @@ async def test_start_data_export(rpc_client: RabbitMQRPCClient, faker: Faker): rpc_namespace=STORAGE_RPC_NAMESPACE, job_name="start_data_export", paths=DataExportTaskStartInput( - user_id=1, location_id=0, paths=[Path(faker.file_path())] + user_id=1, + product_name="osparc", + location_id=0, + paths=[Path(faker.file_path())], ), ) assert isinstance(result, AsyncJobGet)