diff --git a/api/specs/web-server/_long_running_tasks.py b/api/specs/web-server/_long_running_tasks.py index 77b979b2e3e8..6f43395da3f9 100644 --- a/api/specs/web-server/_long_running_tasks.py +++ b/api/specs/web-server/_long_running_tasks.py @@ -12,8 +12,8 @@ from servicelib.aiohttp.long_running_tasks._routes import _PathParam from servicelib.long_running_tasks.models import TaskGet, TaskStatus from simcore_service_webserver._meta import API_VTAG -from simcore_service_webserver.tasks._exception_handlers import ( - _TO_HTTP_ERROR_MAP as export_data_http_error_map, +from simcore_service_webserver.tasks._controller._rest_exceptions import ( + _TO_HTTP_ERROR_MAP, ) router = APIRouter( @@ -21,18 +21,15 @@ tags=[ "long-running-tasks", ], + responses={ + i.status_code: {"model": EnvelopedError} for i in _TO_HTTP_ERROR_MAP.values() + }, ) -_export_data_responses: dict[int | str, dict[str, Any]] = { - i.status_code: {"model": EnvelopedError} - for i in export_data_http_error_map.values() -} - @router.get( "/tasks", response_model=Envelope[list[TaskGet]], - responses=_export_data_responses, ) def get_async_jobs(): """Lists all long running tasks""" @@ -41,7 +38,6 @@ def get_async_jobs(): @router.get( "/tasks/{task_id}", response_model=Envelope[TaskStatus], - responses=_export_data_responses, ) def get_async_job_status( _path_params: Annotated[_PathParam, Depends()], @@ -51,7 +47,6 @@ def get_async_job_status( @router.delete( "/tasks/{task_id}", - responses=_export_data_responses, status_code=status.HTTP_204_NO_CONTENT, ) def cancel_async_job( @@ -63,7 +58,6 @@ def cancel_async_job( @router.get( "/tasks/{task_id}/result", response_model=Any, - responses=_export_data_responses, ) def get_async_job_result( _path_params: Annotated[_PathParam, Depends()], diff --git a/api/specs/web-server/_storage.py b/api/specs/web-server/_storage.py index 52cefbd0b66f..1252e9420a81 100644 --- a/api/specs/web-server/_storage.py +++ b/api/specs/web-server/_storage.py @@ -4,7 +4,7 @@ # pylint: disable=too-many-arguments -from typing import Annotated, Any, TypeAlias +from typing import Annotated, TypeAlias from fastapi import APIRouter, Depends, Query, status from models_library.api_schemas_long_running_tasks.tasks import ( @@ -35,8 +35,8 @@ from servicelib.fastapi.rest_pagination import CustomizedPathsCursorPage from simcore_service_webserver._meta import API_VTAG from simcore_service_webserver.storage.schemas import DatasetMetaData, FileMetaData -from simcore_service_webserver.tasks._exception_handlers import ( - _TO_HTTP_ERROR_MAP as export_data_http_error_map, +from simcore_service_webserver.tasks._controller._rest_exceptions import ( + _TO_HTTP_ERROR_MAP, ) router = APIRouter( @@ -220,19 +220,14 @@ async def is_completed_upload_file( """Returns state of upload completion""" -# data export -_export_data_responses: dict[int | str, dict[str, Any]] = { - i.status_code: {"model": EnvelopedError} - for i in export_data_http_error_map.values() -} - - @router.post( "/storage/locations/{location_id}/export-data", response_model=Envelope[TaskGet], name="export_data", description="Export data", - responses=_export_data_responses, + responses={ + i.status_code: {"model": EnvelopedError} for i in _TO_HTTP_ERROR_MAP.values() + }, ) async def export_data(export_data: DataExportPost, location_id: LocationID): """Trigger data export. Returns async job id for getting status and results""" diff --git a/packages/celery-library/src/celery_library/backends/redis.py b/packages/celery-library/src/celery_library/backends/redis.py index ac7aa7690266..e7a99b85a057 100644 --- a/packages/celery-library/src/celery_library/backends/redis.py +++ b/packages/celery-library/src/celery_library/backends/redis.py @@ -1,5 +1,6 @@ import contextlib import logging +from dataclasses import dataclass from datetime import timedelta from typing import TYPE_CHECKING, Final @@ -10,27 +11,28 @@ ExecutionMetadata, OwnerMetadata, Task, - TaskInfoStore, TaskKey, + TaskStore, ) from servicelib.redis import RedisClientSDK, handle_redis_returns_union_types -_CELERY_TASK_INFO_PREFIX: Final[str] = "celery-task-info-" +_CELERY_TASK_PREFIX: Final[str] = "celery-task-" _CELERY_TASK_ID_KEY_ENCODING = "utf-8" _CELERY_TASK_SCAN_COUNT_PER_BATCH: Final[int] = 1000 _CELERY_TASK_METADATA_KEY: Final[str] = "metadata" _CELERY_TASK_PROGRESS_KEY: Final[str] = "progress" + _logger = logging.getLogger(__name__) -def _build_key(task_key: TaskKey) -> str: - return _CELERY_TASK_INFO_PREFIX + task_key +def _build_redis_task_key(task_key: TaskKey) -> str: + return _CELERY_TASK_PREFIX + task_key -class RedisTaskInfoStore: - def __init__(self, redis_client_sdk: RedisClientSDK) -> None: - self._redis_client_sdk = redis_client_sdk +@dataclass(frozen=True) +class RedisTaskStore: + _redis_client_sdk: RedisClientSDK async def create_task( self, @@ -38,7 +40,7 @@ async def create_task( execution_metadata: ExecutionMetadata, expiry: timedelta, ) -> None: - redis_key = _build_key(task_key) + redis_key = _build_redis_task_key(task_key) await handle_redis_returns_union_types( self._redis_client_sdk.redis.hset( name=redis_key, @@ -54,7 +56,8 @@ async def create_task( async def get_task_metadata(self, task_key: TaskKey) -> ExecutionMetadata | None: raw_result = await handle_redis_returns_union_types( self._redis_client_sdk.redis.hget( - _build_key(task_key), _CELERY_TASK_METADATA_KEY + _build_redis_task_key(task_key), + _CELERY_TASK_METADATA_KEY, ) ) if not raw_result: @@ -73,7 +76,8 @@ async def get_task_metadata(self, task_key: TaskKey) -> ExecutionMetadata | None async def get_task_progress(self, task_key: TaskKey) -> ProgressReport | None: raw_result = await handle_redis_returns_union_types( self._redis_client_sdk.redis.hget( - _build_key(task_key), _CELERY_TASK_PROGRESS_KEY + _build_redis_task_key(task_key), + _CELERY_TASK_PROGRESS_KEY, ) ) if not raw_result: @@ -90,7 +94,7 @@ async def get_task_progress(self, task_key: TaskKey) -> ProgressReport | None: return None async def list_tasks(self, owner_metadata: OwnerMetadata) -> list[Task]: - search_key = _CELERY_TASK_INFO_PREFIX + owner_metadata.model_dump_task_key( + search_key = _CELERY_TASK_PREFIX + owner_metadata.model_dump_task_key( task_uuid=WILDCARD ) @@ -127,24 +131,28 @@ async def list_tasks(self, owner_metadata: OwnerMetadata) -> list[Task]: return tasks async def remove_task(self, task_key: TaskKey) -> None: - await self._redis_client_sdk.redis.delete(_build_key(task_key)) + await self._redis_client_sdk.redis.delete( + _build_redis_task_key(task_key), + ) async def set_task_progress( self, task_key: TaskKey, report: ProgressReport ) -> None: await handle_redis_returns_union_types( self._redis_client_sdk.redis.hset( - name=_build_key(task_key), + name=_build_redis_task_key(task_key), key=_CELERY_TASK_PROGRESS_KEY, value=report.model_dump_json(), ) ) async def task_exists(self, task_key: TaskKey) -> bool: - n = await self._redis_client_sdk.redis.exists(_build_key(task_key)) + n = await self._redis_client_sdk.redis.exists( + _build_redis_task_key(task_key), + ) assert isinstance(n, int) # nosec return n > 0 if TYPE_CHECKING: - _: type[TaskInfoStore] = RedisTaskInfoStore + _: type[TaskStore] = RedisTaskStore diff --git a/packages/celery-library/src/celery_library/errors.py b/packages/celery-library/src/celery_library/errors.py index 83aff4443226..11dc1c1b81fe 100644 --- a/packages/celery-library/src/celery_library/errors.py +++ b/packages/celery-library/src/celery_library/errors.py @@ -1,6 +1,8 @@ import base64 import pickle +from functools import wraps +from celery.exceptions import CeleryError # type: ignore[import-untyped] from common_library.errors_classes import OsparcErrorMixin @@ -32,3 +34,18 @@ class TaskSubmissionError(OsparcErrorMixin, Exception): class TaskNotFoundError(OsparcErrorMixin, Exception): msg_template = "Task with uuid '{task_uuid}' and owner_metadata '{owner_metadata}' was not found" + + +class TaskManagerError(OsparcErrorMixin, Exception): + msg_template = "An internal error occurred" + + +def handle_celery_errors(func): + @wraps(func) + async def wrapper(*args, **kwargs): + try: + return await func(*args, **kwargs) + except CeleryError as exc: + raise TaskManagerError from exc + + return wrapper diff --git a/packages/celery-library/src/celery_library/task_manager.py b/packages/celery-library/src/celery_library/task_manager.py index fb1e69f2d847..ad6e15844cee 100644 --- a/packages/celery-library/src/celery_library/task_manager.py +++ b/packages/celery-library/src/celery_library/task_manager.py @@ -12,17 +12,17 @@ ExecutionMetadata, OwnerMetadata, Task, - TaskInfoStore, TaskKey, TaskState, TaskStatus, + TaskStore, TaskUUID, ) from servicelib.celery.task_manager import TaskManager from servicelib.logging_utils import log_context from settings_library.celery import CelerySettings -from .errors import TaskNotFoundError, TaskSubmissionError +from .errors import TaskNotFoundError, TaskSubmissionError, handle_celery_errors _logger = logging.getLogger(__name__) @@ -35,8 +35,9 @@ class CeleryTaskManager: _celery_app: Celery _celery_settings: CelerySettings - _task_info_store: TaskInfoStore + _task_info_store: TaskStore + @handle_celery_errors async def submit_task( self, execution_metadata: ExecutionMetadata, @@ -85,6 +86,7 @@ async def submit_task( return task_uuid + @handle_celery_errors async def cancel_task( self, owner_metadata: OwnerMetadata, task_uuid: TaskUUID ) -> None: @@ -109,6 +111,7 @@ async def task_exists(self, task_key: TaskKey) -> bool: def _forget_task(self, task_key: TaskKey) -> None: self._celery_app.AsyncResult(task_key).forget() + @handle_celery_errors async def get_task_result( self, owner_metadata: OwnerMetadata, task_uuid: TaskUUID ) -> Any: @@ -154,6 +157,7 @@ async def _get_task_progress_report( def _get_task_celery_state(self, task_key: TaskKey) -> TaskState: return TaskState(self._celery_app.AsyncResult(task_key).state) + @handle_celery_errors async def get_task_status( self, owner_metadata: OwnerMetadata, task_uuid: TaskUUID ) -> TaskStatus: @@ -177,6 +181,7 @@ async def get_task_status( ), ) + @handle_celery_errors async def list_tasks(self, owner_metadata: OwnerMetadata) -> list[Task]: with log_context( _logger, @@ -185,6 +190,7 @@ async def list_tasks(self, owner_metadata: OwnerMetadata) -> list[Task]: ): return await self._task_info_store.list_tasks(owner_metadata) + @handle_celery_errors async def set_task_progress( self, task_key: TaskKey, report: ProgressReport ) -> None: diff --git a/packages/celery-library/tests/conftest.py b/packages/celery-library/tests/conftest.py index 8e8bc9768205..e37f7d003f1e 100644 --- a/packages/celery-library/tests/conftest.py +++ b/packages/celery-library/tests/conftest.py @@ -15,7 +15,7 @@ ) from celery.signals import worker_init, worker_shutdown from celery.worker.worker import WorkController -from celery_library.backends.redis import RedisTaskInfoStore +from celery_library.backends.redis import RedisTaskStore from celery_library.signals import on_worker_init, on_worker_shutdown from celery_library.task_manager import CeleryTaskManager from celery_library.types import register_celery_types @@ -66,7 +66,7 @@ async def run_until_shutdown( self._task_manager = CeleryTaskManager( self._app, self._settings, - RedisTaskInfoStore(redis_client_sdk), + RedisTaskStore(redis_client_sdk), ) startup_completed_event.set() @@ -156,11 +156,11 @@ async def mock_celery_app(celery_config: dict[str, Any]) -> Celery: @pytest.fixture -async def celery_task_manager( +async def task_manager( mock_celery_app: Celery, celery_settings: CelerySettings, use_in_memory_redis: RedisSettings, -) -> AsyncIterator[CeleryTaskManager]: +) -> AsyncIterator[TaskManager]: register_celery_types() try: @@ -173,7 +173,7 @@ async def celery_task_manager( yield CeleryTaskManager( mock_celery_app, celery_settings, - RedisTaskInfoStore(redis_client_sdk), + RedisTaskStore(redis_client_sdk), ) finally: await redis_client_sdk.shutdown() diff --git a/packages/celery-library/tests/unit/test_async_jobs.py b/packages/celery-library/tests/unit/test_async_jobs.py index 51e6ab0c02a2..d6a7c82cb2fe 100644 --- a/packages/celery-library/tests/unit/test_async_jobs.py +++ b/packages/celery-library/tests/unit/test_async_jobs.py @@ -138,13 +138,13 @@ async def async_job(task: Task, task_key: TaskKey, action: Action, payload: Any) @pytest.fixture async def register_rpc_routes( - async_jobs_rabbitmq_rpc_client: RabbitMQRPCClient, celery_task_manager: TaskManager + async_jobs_rabbitmq_rpc_client: RabbitMQRPCClient, task_manager: TaskManager ) -> None: await async_jobs_rabbitmq_rpc_client.register_router( - _async_jobs.router, ASYNC_JOBS_RPC_NAMESPACE, task_manager=celery_task_manager + _async_jobs.router, ASYNC_JOBS_RPC_NAMESPACE, task_manager=task_manager ) await async_jobs_rabbitmq_rpc_client.register_router( - router, ASYNC_JOBS_RPC_NAMESPACE, task_manager=celery_task_manager + router, ASYNC_JOBS_RPC_NAMESPACE, task_manager=task_manager ) diff --git a/packages/celery-library/tests/unit/test_tasks.py b/packages/celery-library/tests/unit/test_task_manager.py similarity index 81% rename from packages/celery-library/tests/unit/test_tasks.py rename to packages/celery-library/tests/unit/test_task_manager.py index e892d6f24282..44a4de374e7b 100644 --- a/packages/celery-library/tests/unit/test_tasks.py +++ b/packages/celery-library/tests/unit/test_task_manager.py @@ -28,6 +28,7 @@ TaskUUID, Wildcard, ) +from servicelib.celery.task_manager import TaskManager from servicelib.logging_utils import log_context from tenacity import Retrying, retry_if_exception_type, stop_after_delay, wait_fixed @@ -100,13 +101,13 @@ def _(celery_app: Celery) -> None: async def test_submitting_task_calling_async_function_results_with_success_state( - celery_task_manager: CeleryTaskManager, + task_manager: TaskManager, with_celery_worker: WorkController, ): owner_metadata = MyOwnerMetadata(user_id=42, owner="test-owner") - task_uuid = await celery_task_manager.submit_task( + task_uuid = await task_manager.submit_task( ExecutionMetadata( name=fake_file_processor.__name__, ), @@ -120,27 +121,25 @@ async def test_submitting_task_calling_async_function_results_with_success_state stop=stop_after_delay(30), ): with attempt: - status = await celery_task_manager.get_task_status( - owner_metadata, task_uuid - ) + status = await task_manager.get_task_status(owner_metadata, task_uuid) assert status.task_state == TaskState.SUCCESS assert ( - await celery_task_manager.get_task_status(owner_metadata, task_uuid) + await task_manager.get_task_status(owner_metadata, task_uuid) ).task_state == TaskState.SUCCESS assert ( - await celery_task_manager.get_task_result(owner_metadata, task_uuid) + await task_manager.get_task_result(owner_metadata, task_uuid) ) == "archive.zip" async def test_submitting_task_with_failure_results_with_error( - celery_task_manager: CeleryTaskManager, + task_manager: TaskManager, with_celery_worker: WorkController, ): owner_metadata = MyOwnerMetadata(user_id=42, owner="test-owner") - task_uuid = await celery_task_manager.submit_task( + task_uuid = await task_manager.submit_task( ExecutionMetadata( name=failure_task.__name__, ), @@ -154,23 +153,21 @@ async def test_submitting_task_with_failure_results_with_error( ): with attempt: - raw_result = await celery_task_manager.get_task_result( - owner_metadata, task_uuid - ) + raw_result = await task_manager.get_task_result(owner_metadata, task_uuid) assert isinstance(raw_result, TransferrableCeleryError) - raw_result = await celery_task_manager.get_task_result(owner_metadata, task_uuid) + raw_result = await task_manager.get_task_result(owner_metadata, task_uuid) assert f"{raw_result}" == "Something strange happened: BOOM!" async def test_cancelling_a_running_task_aborts_and_deletes( - celery_task_manager: CeleryTaskManager, + task_manager: TaskManager, with_celery_worker: WorkController, ): owner_metadata = MyOwnerMetadata(user_id=42, owner="test-owner") - task_uuid = await celery_task_manager.submit_task( + task_uuid = await task_manager.submit_task( ExecutionMetadata( name=dreamer_task.__name__, ), @@ -179,22 +176,22 @@ async def test_cancelling_a_running_task_aborts_and_deletes( await asyncio.sleep(3.0) - await celery_task_manager.cancel_task(owner_metadata, task_uuid) + await task_manager.cancel_task(owner_metadata, task_uuid) with pytest.raises(TaskNotFoundError): - await celery_task_manager.get_task_status(owner_metadata, task_uuid) + await task_manager.get_task_status(owner_metadata, task_uuid) - assert task_uuid not in await celery_task_manager.list_tasks(owner_metadata) + assert task_uuid not in await task_manager.list_tasks(owner_metadata) async def test_listing_task_uuids_contains_submitted_task( - celery_task_manager: CeleryTaskManager, + task_manager: CeleryTaskManager, with_celery_worker: WorkController, ): owner_metadata = MyOwnerMetadata(user_id=42, owner="test-owner") - task_uuid = await celery_task_manager.submit_task( + task_uuid = await task_manager.submit_task( ExecutionMetadata( name=dreamer_task.__name__, ), @@ -207,15 +204,15 @@ async def test_listing_task_uuids_contains_submitted_task( stop=stop_after_delay(10), ): with attempt: - tasks = await celery_task_manager.list_tasks(owner_metadata) + tasks = await task_manager.list_tasks(owner_metadata) assert any(task.uuid == task_uuid for task in tasks) - tasks = await celery_task_manager.list_tasks(owner_metadata) + tasks = await task_manager.list_tasks(owner_metadata) assert any(task.uuid == task_uuid for task in tasks) async def test_filtering_listing_tasks( - celery_task_manager: CeleryTaskManager, + task_manager: TaskManager, with_celery_worker: WorkController, ): class MyOwnerMetadata(OwnerMetadata): @@ -232,7 +229,7 @@ class MyOwnerMetadata(OwnerMetadata): owner_metadata = MyOwnerMetadata( user_id=user_id, product_name=_faker.word(), owner=_owner ) - task_uuid = await celery_task_manager.submit_task( + task_uuid = await task_manager.submit_task( ExecutionMetadata( name=dreamer_task.__name__, ), @@ -247,7 +244,7 @@ class MyOwnerMetadata(OwnerMetadata): product_name=_faker.word(), owner=_owner, ) - task_uuid = await celery_task_manager.submit_task( + task_uuid = await task_manager.submit_task( ExecutionMetadata( name=dreamer_task.__name__, ), @@ -260,9 +257,9 @@ class MyOwnerMetadata(OwnerMetadata): product_name="*", owner=_owner, ) - tasks = await celery_task_manager.list_tasks(search_owner_metadata) + tasks = await task_manager.list_tasks(search_owner_metadata) assert expected_task_uuids == {task.uuid for task in tasks} finally: # clean up all tasks. this should ideally be done in the fixture for task_uuid, owner_metadata in all_tasks: - await celery_task_manager.cancel_task(owner_metadata, task_uuid) + await task_manager.cancel_task(owner_metadata, task_uuid) diff --git a/packages/service-library/src/servicelib/celery/models.py b/packages/service-library/src/servicelib/celery/models.py index ae005b497bca..68cdfc2e5811 100644 --- a/packages/service-library/src/servicelib/celery/models.py +++ b/packages/service-library/src/servicelib/celery/models.py @@ -181,7 +181,7 @@ def _update_json_schema_extra(schema: JsonDict) -> None: model_config = ConfigDict(json_schema_extra=_update_json_schema_extra) -class TaskInfoStore(Protocol): +class TaskStore(Protocol): async def create_task( self, task_key: TaskKey, @@ -202,7 +202,9 @@ async def list_tasks(self, owner_metadata: OwnerMetadata) -> list[Task]: ... async def remove_task(self, task_key: TaskKey) -> None: ... async def set_task_progress( - self, task_key: TaskKey, report: ProgressReport + self, + task_key: TaskKey, + report: ProgressReport, ) -> None: ... diff --git a/packages/service-library/src/servicelib/celery/task_manager.py b/packages/service-library/src/servicelib/celery/task_manager.py index ee745f78765e..5135f3168405 100644 --- a/packages/service-library/src/servicelib/celery/task_manager.py +++ b/packages/service-library/src/servicelib/celery/task_manager.py @@ -2,7 +2,7 @@ from models_library.progress_bar import ProgressReport -from ..celery.models import ( +from .models import ( ExecutionMetadata, OwnerMetadata, Task, @@ -26,8 +26,6 @@ async def cancel_task( self, owner_metadata: OwnerMetadata, task_uuid: TaskUUID ) -> None: ... - async def task_exists(self, task_key: TaskKey) -> bool: ... - async def get_task_result( self, owner_metadata: OwnerMetadata, task_uuid: TaskUUID ) -> Any: ... @@ -41,3 +39,5 @@ async def list_tasks(self, owner_metadata: OwnerMetadata) -> list[Task]: ... async def set_task_progress( self, task_key: TaskKey, report: ProgressReport ) -> None: ... + + async def task_exists(self, task_key: TaskKey) -> bool: ... diff --git a/packages/service-library/src/servicelib/mimetype_constants.py b/packages/service-library/src/servicelib/mimetype_constants.py index ffde9a3f7b30..a6e88d94407f 100644 --- a/packages/service-library/src/servicelib/mimetype_constants.py +++ b/packages/service-library/src/servicelib/mimetype_constants.py @@ -7,6 +7,7 @@ SEE https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types """ + from typing import Final # NOTE: mimetypes (https://docs.python.org/3/library/mimetypes.html) is already a module in python diff --git a/services/api-server/src/simcore_service_api_server/clients/celery_task_manager.py b/services/api-server/src/simcore_service_api_server/clients/celery_task_manager.py index a8d2cb13b554..13829cfdd303 100644 --- a/services/api-server/src/simcore_service_api_server/clients/celery_task_manager.py +++ b/services/api-server/src/simcore_service_api_server/clients/celery_task_manager.py @@ -1,6 +1,6 @@ import logging -from celery_library.backends.redis import RedisTaskInfoStore +from celery_library.backends.redis import RedisTaskStore from celery_library.common import create_app from celery_library.task_manager import CeleryTaskManager from celery_library.types import register_celery_types, register_pydantic_types @@ -30,7 +30,7 @@ async def on_startup() -> None: app.state.task_manager = CeleryTaskManager( create_app(settings), settings, - RedisTaskInfoStore(redis_client_sdk), + RedisTaskStore(redis_client_sdk), ) register_celery_types() diff --git a/services/docker-compose.yml b/services/docker-compose.yml index 86eae20daf93..44ef1e437fd7 100644 --- a/services/docker-compose.yml +++ b/services/docker-compose.yml @@ -972,6 +972,7 @@ services: WEBSERVER_ACTIVITY: ${WB_DB_EL_ACTIVITY} WEBSERVER_ANNOUNCEMENTS: ${WB_DB_EL_ANNOUNCEMENTS} WEBSERVER_CATALOG: ${WB_DB_EL_CATALOG} + WEBSERVER_CELERY: "null" WEBSERVER_DB_LISTENER: ${WB_DB_EL_DB_LISTENER} WEBSERVER_DIAGNOSTICS: ${WB_DB_EL_DIAGNOSTICS} WEBSERVER_EMAIL: ${WB_DB_EL_EMAIL} @@ -1083,6 +1084,7 @@ services: WEBSERVER_ACTIVITY: ${WB_GC_ACTIVITY} WEBSERVER_ANNOUNCEMENTS: ${WB_GC_ANNOUNCEMENTS} WEBSERVER_CATALOG: ${WB_GC_CATALOG} + WEBSERVER_CELERY: "null" WEBSERVER_DB_LISTENER: ${WB_GC_DB_LISTENER} WEBSERVER_DIAGNOSTICS: ${WB_GC_DIAGNOSTICS} WEBSERVER_EMAIL: ${WB_GC_EMAIL} @@ -1159,6 +1161,7 @@ services: WEBSERVER_ACTIVITY: "null" WEBSERVER_ANNOUNCEMENTS: 0 WEBSERVER_CATALOG: "null" + WEBSERVER_CELERY: "null" WEBSERVER_DB_LISTENER: 0 WEBSERVER_DIRECTOR_V2: "null" WEBSERVER_EMAIL: "null" diff --git a/services/storage/src/simcore_service_storage/modules/celery/__init__.py b/services/storage/src/simcore_service_storage/modules/celery/__init__.py index 0dcb3a2ea5e6..3f292337c73e 100644 --- a/services/storage/src/simcore_service_storage/modules/celery/__init__.py +++ b/services/storage/src/simcore_service_storage/modules/celery/__init__.py @@ -1,6 +1,6 @@ import logging -from celery_library.backends.redis import RedisTaskInfoStore +from celery_library.backends.redis import RedisTaskStore from celery_library.common import create_app from celery_library.task_manager import CeleryTaskManager from celery_library.types import register_celery_types, register_pydantic_types @@ -34,7 +34,7 @@ async def on_startup() -> None: app.state.task_manager = CeleryTaskManager( create_app(settings), settings, - RedisTaskInfoStore(redis_client_sdk), + RedisTaskStore(redis_client_sdk), ) register_celery_types() diff --git a/services/web/server/requirements/_base.in b/services/web/server/requirements/_base.in index 2af5fb69ea3d..3bb87873e287 100644 --- a/services/web/server/requirements/_base.in +++ b/services/web/server/requirements/_base.in @@ -9,6 +9,7 @@ # - Added as constraints instead of requirements in order to avoid polluting base.txt # - Will be installed when prod.txt or dev.txt # +--requirement ../../../../packages/celery-library/requirements/_base.in --requirement ../../../../packages/common-library/requirements/_base.in --requirement ../../../../packages/models-library/requirements/_base.in --requirement ../../../../packages/notifications-library/requirements/_base.in diff --git a/services/web/server/requirements/_base.txt b/services/web/server/requirements/_base.txt index a8e2f003e025..3ce7b5eeec19 100644 --- a/services/web/server/requirements/_base.txt +++ b/services/web/server/requirements/_base.txt @@ -1,25 +1,30 @@ aio-pika==9.1.2 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/_base.in aiocache==0.11.1 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/_base.in # -r requirements/_base.in aiodebug==2.3.0 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/_base.in aiodocker==0.21.0 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/service-library/requirements/_base.in aiofiles==0.8.0 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/notifications-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/service-library/requirements/_base.in @@ -29,6 +34,18 @@ aiohappyeyeballs==2.6.1 # via aiohttp aiohttp==3.12.12 # via + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../requirements/constraints.txt @@ -95,6 +112,8 @@ alembic==1.8.1 # -r requirements/../../../../packages/notifications-library/requirements/../../../packages/postgres-database/requirements/_base.in # -r requirements/../../../../packages/postgres-database/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/postgres-database/requirements/_base.in +amqp==5.3.1 + # via kombu annotated-types==0.7.0 # via pydantic anyio==4.3.0 @@ -106,6 +125,9 @@ appdirs==1.4.4 # via pint arrow==1.3.0 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/models-library/requirements/_base.in # -r requirements/../../../../packages/notifications-library/requirements/../../../packages/models-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/../../../packages/models-library/requirements/_base.in @@ -126,10 +148,26 @@ attrs==21.4.0 # jsonschema bidict==0.22.0 # via python-socketio +billiard==4.2.1 + # via celery captcha==0.5.0 # via -r requirements/_base.in +celery==5.5.3 + # via -r requirements/../../../../packages/celery-library/requirements/_base.in certifi==2023.7.22 # via + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../requirements/constraints.txt @@ -174,9 +212,32 @@ cffi==1.17.1 charset-normalizer==2.0.12 # via requests click==8.2.1 - # via typer + # via + # celery + # click-didyoumean + # click-plugins + # click-repl + # typer +click-didyoumean==0.3.1 + # via celery +click-plugins==1.1.1.2 + # via celery +click-repl==0.3.0 + # via celery cryptography==41.0.7 # via + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../requirements/constraints.txt @@ -229,6 +290,7 @@ fast-depends==2.4.12 # via faststream faststream==0.5.31 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/service-library/requirements/_base.in flexcache==0.3 @@ -258,6 +320,18 @@ httpcore==1.0.9 # via httpx httpx==0.28.1 # via + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../requirements/constraints.txt @@ -309,6 +383,18 @@ jinja-app-loader==1.0.2 # via -r requirements/_base.in jinja2==3.1.6 # via + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../requirements/constraints.txt @@ -352,6 +438,8 @@ jsondiff==2.0.0 # via -r requirements/_base.in jsonref==1.1.0 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/_base.in # -r requirements/../../../../packages/models-library/requirements/_base.in # -r requirements/../../../../packages/notifications-library/requirements/../../../packages/models-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/../../../packages/models-library/requirements/_base.in @@ -359,14 +447,30 @@ jsonref==1.1.0 # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/_base.in jsonschema==3.2.0 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/_base.in # -r requirements/../../../../packages/models-library/requirements/_base.in # -r requirements/../../../../packages/notifications-library/requirements/../../../packages/models-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/../../../packages/models-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/_aiohttp.in # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/models-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/_base.in +kombu==5.5.4 + # via celery mako==1.3.10 # via + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../requirements/constraints.txt @@ -422,6 +526,7 @@ openpyxl==3.0.9 # via -r requirements/_base.in opentelemetry-api==1.34.1 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/service-library/requirements/_base.in # opentelemetry-exporter-otlp-proto-grpc @@ -440,6 +545,7 @@ opentelemetry-api==1.34.1 # opentelemetry-semantic-conventions opentelemetry-exporter-otlp==1.34.1 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/service-library/requirements/_base.in opentelemetry-exporter-otlp-proto-common==1.34.1 @@ -463,6 +569,7 @@ opentelemetry-instrumentation==0.55b1 # opentelemetry-instrumentation-requests opentelemetry-instrumentation-aio-pika==0.55b1 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/service-library/requirements/_base.in opentelemetry-instrumentation-aiohttp-client==0.55b1 @@ -475,20 +582,24 @@ opentelemetry-instrumentation-aiopg==0.55b1 # -r requirements/_base.in opentelemetry-instrumentation-asyncpg==0.55b1 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/service-library/requirements/_base.in opentelemetry-instrumentation-dbapi==0.55b1 # via opentelemetry-instrumentation-aiopg opentelemetry-instrumentation-logging==0.55b1 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/service-library/requirements/_base.in opentelemetry-instrumentation-redis==0.55b1 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/service-library/requirements/_base.in opentelemetry-instrumentation-requests==0.55b1 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/service-library/requirements/_base.in opentelemetry-proto==1.34.1 @@ -498,6 +609,7 @@ opentelemetry-proto==1.34.1 # opentelemetry-exporter-otlp-proto-http opentelemetry-sdk==1.34.1 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/service-library/requirements/_base.in # opentelemetry-exporter-otlp-proto-grpc @@ -521,6 +633,18 @@ orderly-set==5.2.3 # via deepdiff orjson==3.10.0 # via + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../requirements/constraints.txt @@ -557,6 +681,14 @@ orjson==3.10.0 # -c requirements/../../../../packages/simcore-sdk/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/simcore-sdk/requirements/../../../requirements/constraints.txt # -c requirements/../../../../requirements/constraints.txt + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/common-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/_base.in # -r requirements/../../../../packages/common-library/requirements/_base.in # -r requirements/../../../../packages/models-library/requirements/../../../packages/common-library/requirements/_base.in # -r requirements/../../../../packages/models-library/requirements/_base.in @@ -587,6 +719,7 @@ packaging==24.1 # -r requirements/../../../../packages/simcore-sdk/requirements/_base.in # -r requirements/_base.in # gunicorn + # kombu # opentelemetry-instrumentation # swagger-ui-py pamqp==3.2.1 @@ -603,6 +736,8 @@ pint==0.24.3 # -r requirements/_base.in prometheus-client==0.14.1 # via -r requirements/../../../../packages/service-library/requirements/_aiohttp.in +prompt-toolkit==3.0.52 + # via click-repl propcache==0.3.1 # via # aiohttp @@ -613,6 +748,7 @@ protobuf==5.29.5 # opentelemetry-proto psutil==6.0.0 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/service-library/requirements/_base.in psycopg2-binary==2.9.6 @@ -627,6 +763,18 @@ pycryptodome==3.21.0 # via stream-zip pydantic==2.11.7 # via + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../requirements/constraints.txt @@ -663,6 +811,17 @@ pydantic==2.11.7 # -c requirements/../../../../packages/simcore-sdk/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/simcore-sdk/requirements/../../../requirements/constraints.txt # -c requirements/../../../../requirements/constraints.txt + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/common-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/_base.in # -r requirements/../../../../packages/common-library/requirements/_base.in # -r requirements/../../../../packages/models-library/requirements/../../../packages/common-library/requirements/_base.in # -r requirements/../../../../packages/models-library/requirements/_base.in @@ -705,6 +864,14 @@ pydantic-core==2.33.2 # via pydantic pydantic-extra-types==2.10.5 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/common-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/_base.in # -r requirements/../../../../packages/common-library/requirements/_base.in # -r requirements/../../../../packages/models-library/requirements/../../../packages/common-library/requirements/_base.in # -r requirements/../../../../packages/models-library/requirements/_base.in @@ -730,6 +897,18 @@ pydantic-extra-types==2.10.5 # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/_base.in pydantic-settings==2.7.0 # via + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../requirements/constraints.txt @@ -766,6 +945,10 @@ pydantic-settings==2.7.0 # -c requirements/../../../../packages/simcore-sdk/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/simcore-sdk/requirements/../../../requirements/constraints.txt # -c requirements/../../../../requirements/constraints.txt + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/_base.in # -r requirements/../../../../packages/models-library/requirements/_base.in # -r requirements/../../../../packages/notifications-library/requirements/../../../packages/models-library/requirements/_base.in # -r requirements/../../../../packages/notifications-library/requirements/../../../packages/settings-library/requirements/_base.in @@ -780,6 +963,7 @@ pygments==2.15.1 # via rich pyinstrument==4.6.1 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/service-library/requirements/_base.in pyjwt==2.4.0 @@ -789,6 +973,7 @@ pyrsistent==0.18.1 python-dateutil==2.8.2 # via # arrow + # celery # faker python-dotenv==1.0.1 # via pydantic-settings @@ -802,6 +987,18 @@ pytz==2022.1 # via twilio pyyaml==6.0.1 # via + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../requirements/constraints.txt @@ -838,11 +1035,24 @@ pyyaml==6.0.1 # -c requirements/../../../../packages/simcore-sdk/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/simcore-sdk/requirements/../../../requirements/constraints.txt # -c requirements/../../../../requirements/constraints.txt + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/service-library/requirements/_base.in # swagger-ui-py redis==5.2.1 # via + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../requirements/constraints.txt @@ -879,15 +1089,19 @@ redis==5.2.1 # -c requirements/../../../../packages/simcore-sdk/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/simcore-sdk/requirements/../../../requirements/constraints.txt # -c requirements/../../../../requirements/constraints.txt + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/_base.in + # kombu requests==2.32.4 # via # opentelemetry-exporter-otlp-proto-http # twilio rich==14.1.0 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/_base.in # -r requirements/../../../../packages/notifications-library/requirements/../../../packages/settings-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/../../../packages/settings-library/requirements/_base.in # -r requirements/../../../../packages/settings-library/requirements/_base.in @@ -910,6 +1124,18 @@ sniffio==1.3.1 # via anyio sqlalchemy==1.4.47 # via + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../requirements/constraints.txt @@ -954,22 +1180,26 @@ sqlalchemy==1.4.47 # alembic stream-zip==0.0.83 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/service-library/requirements/_base.in swagger-ui-py==23.9.23 # via -r requirements/_base.in tenacity==8.5.0 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/_base.in # -r requirements/_base.in toolz==0.12.0 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/service-library/requirements/_base.in tqdm==4.64.0 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/simcore-sdk/requirements/_base.in @@ -977,6 +1207,8 @@ twilio==7.12.0 # via -r requirements/_base.in typer==0.16.1 # via + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/_base.in + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/_base.in # -r requirements/../../../../packages/notifications-library/requirements/../../../packages/settings-library/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/../../../packages/settings-library/requirements/_base.in # -r requirements/../../../../packages/settings-library/requirements/_base.in @@ -1004,8 +1236,22 @@ typing-extensions==4.14.1 # typing-inspection typing-inspection==0.4.1 # via pydantic +tzdata==2025.2 + # via kombu urllib3==2.5.0 # via + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt + # -c requirements/../../../../packages/celery-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt # -c requirements/../../../../packages/models-library/requirements/../../../requirements/constraints.txt @@ -1045,6 +1291,13 @@ urllib3==2.5.0 # requests uvloop==0.21.0 # via -r requirements/_base.in +vine==5.1.0 + # via + # amqp + # celery + # kombu +wcwidth==0.2.13 + # via prompt-toolkit werkzeug==2.1.2 # via -r requirements/../../../../packages/service-library/requirements/_aiohttp.in wrapt==1.16.0 @@ -1061,6 +1314,7 @@ wsproto==1.2.0 yarl==1.20.0 # via # -c requirements/./constraints.txt + # -r requirements/../../../../packages/celery-library/requirements/../../../packages/service-library/requirements/_base.in # -r requirements/../../../../packages/notifications-library/requirements/../../../packages/postgres-database/requirements/_base.in # -r requirements/../../../../packages/postgres-database/requirements/_base.in # -r requirements/../../../../packages/service-library/requirements/_base.in diff --git a/services/web/server/requirements/ci.txt b/services/web/server/requirements/ci.txt index 447134bf0f4f..433c7802e1f2 100644 --- a/services/web/server/requirements/ci.txt +++ b/services/web/server/requirements/ci.txt @@ -14,6 +14,7 @@ --requirement _tools.txt # installs this repo's packages +simcore-celery-library @ ../../../packages/celery-library simcore-common-library @ ../../../packages/common-library simcore-models-library @ ../../../packages/models-library simcore-notifications-library @ ../../../packages/notifications-library/ diff --git a/services/web/server/requirements/dev.txt b/services/web/server/requirements/dev.txt index f155c2fba7a0..cf73b6c53995 100644 --- a/services/web/server/requirements/dev.txt +++ b/services/web/server/requirements/dev.txt @@ -12,6 +12,7 @@ --requirement _tools.txt # installs this repo's packages +--editable ../../../packages/celery-library/ --editable ../../../packages/common-library/ --editable ../../../packages/models-library/ --editable ../../../packages/notifications-library/ diff --git a/services/web/server/requirements/prod.txt b/services/web/server/requirements/prod.txt index edaeb9dd2b67..7379d1811fb7 100644 --- a/services/web/server/requirements/prod.txt +++ b/services/web/server/requirements/prod.txt @@ -10,6 +10,7 @@ --requirement _base.txt # installs this repo's packages +simcore-celery-library @ ../../../packages/celery-library simcore-common-library @ ../../../packages/common-library simcore-models-library @ ../../../packages/models-library simcore-notifications-library @ ../../../packages/notifications-library/ diff --git a/services/web/server/src/simcore_service_webserver/application.py b/services/web/server/src/simcore_service_webserver/application.py index c996e7af5ad8..67cb5bf49f1c 100644 --- a/services/web/server/src/simcore_service_webserver/application.py +++ b/services/web/server/src/simcore_service_webserver/application.py @@ -21,6 +21,7 @@ from .api_keys.plugin import setup_api_keys from .application_settings import get_application_settings, setup_settings from .catalog.plugin import setup_catalog +from .celery.plugin import setup_celery from .collaboration.bootstrap import ( setup_realtime_collaboration, ) @@ -188,6 +189,9 @@ def create_application() -> web.Application: setup_exporter(app) setup_realtime_collaboration(app) + # Celery + setup_celery(app) + # NOTE: *last* events app.on_startup.append(_create_welcome_banner(WELCOME_MSG)) app.on_shutdown.append(_create_finished_banner()) diff --git a/services/web/server/src/simcore_service_webserver/application_settings.py b/services/web/server/src/simcore_service_webserver/application_settings.py index ee0b96d700b2..3d08171b8f92 100644 --- a/services/web/server/src/simcore_service_webserver/application_settings.py +++ b/services/web/server/src/simcore_service_webserver/application_settings.py @@ -19,6 +19,7 @@ from pydantic.fields import Field from servicelib.logging_utils import LogLevelInt from settings_library.application import BaseApplicationSettings +from settings_library.celery import CelerySettings from settings_library.email import SMTPSettings from settings_library.postgres import PostgresSettings from settings_library.prometheus import PrometheusSettings @@ -205,6 +206,13 @@ class ApplicationSettings(BaseApplicationSettings, MixinLoggingSettings): description="catalog service client's plugin", ), ] + WEBSERVER_CELERY: Annotated[ + CelerySettings | None, + Field( + json_schema_extra={"auto_default_from_env": True}, + description="celery plugin", + ), + ] WEBSERVER_DB: Annotated[ PostgresSettings | None, Field( diff --git a/services/web/server/src/simcore_service_webserver/celery/__init__.py b/services/web/server/src/simcore_service_webserver/celery/__init__.py new file mode 100644 index 000000000000..05072896ee7a --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/celery/__init__.py @@ -0,0 +1,3 @@ +from ._task_manager import get_task_manager + +__all__: tuple[str, ...] = ("get_task_manager",) diff --git a/services/web/server/src/simcore_service_webserver/celery/_task_manager.py b/services/web/server/src/simcore_service_webserver/celery/_task_manager.py new file mode 100644 index 000000000000..adc06a8e219a --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/celery/_task_manager.py @@ -0,0 +1,41 @@ +import logging +from typing import Final + +from aiohttp import web +from celery_library.backends.redis import RedisTaskStore +from celery_library.common import create_app +from celery_library.task_manager import CeleryTaskManager +from celery_library.types import register_celery_types +from servicelib.celery.task_manager import TaskManager +from servicelib.logging_utils import log_context +from settings_library.celery import CelerySettings + +from ..redis import get_redis_celery_tasks_client_sdk +from .settings import get_plugin_settings + +_logger = logging.getLogger(__name__) + +_APP_CELERY_TASK_MANAGER_KEY: Final = web.AppKey( + CeleryTaskManager.__name__, CeleryTaskManager +) + + +async def setup_task_manager(app: web.Application): + with log_context(_logger, logging.INFO, "Setting up Celery task manager"): + celery_settings: CelerySettings = get_plugin_settings(app) + + redis_client_sdk = get_redis_celery_tasks_client_sdk(app) + celery_app = create_app(celery_settings) + + app[_APP_CELERY_TASK_MANAGER_KEY] = CeleryTaskManager( + celery_app, + celery_settings, + RedisTaskStore(redis_client_sdk), + ) + register_celery_types() + + yield + + +def get_task_manager(app: web.Application) -> TaskManager: + return app[_APP_CELERY_TASK_MANAGER_KEY] diff --git a/services/web/server/src/simcore_service_webserver/celery/plugin.py b/services/web/server/src/simcore_service_webserver/celery/plugin.py new file mode 100644 index 000000000000..bb5f2fb66ea8 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/celery/plugin.py @@ -0,0 +1,18 @@ +import logging + +from aiohttp import web + +from ..application_setup import ModuleCategory, app_setup_func +from ._task_manager import setup_task_manager + +_logger = logging.getLogger(__name__) + + +@app_setup_func( + __name__, + ModuleCategory.ADDON, + settings_name="WEBSERVER_CELERY", + logger=_logger, +) +def setup_celery(app: web.Application): + app.cleanup_ctx.append(setup_task_manager) diff --git a/services/web/server/src/simcore_service_webserver/celery/settings.py b/services/web/server/src/simcore_service_webserver/celery/settings.py new file mode 100644 index 000000000000..924366c32202 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/celery/settings.py @@ -0,0 +1,11 @@ +from aiohttp import web +from settings_library.celery import CelerySettings + +from ..application_keys import APP_SETTINGS_APPKEY + + +def get_plugin_settings(app: web.Application) -> CelerySettings: + settings = app[APP_SETTINGS_APPKEY].WEBSERVER_CELERY + assert settings, "plugin.setup_celery not called?" # nosec + assert isinstance(settings, CelerySettings) # nosec + return settings diff --git a/services/web/server/src/simcore_service_webserver/redis.py b/services/web/server/src/simcore_service_webserver/redis.py index 8136c8b47702..a1dcfb6ff831 100644 --- a/services/web/server/src/simcore_service_webserver/redis.py +++ b/services/web/server/src/simcore_service_webserver/redis.py @@ -43,6 +43,7 @@ async def setup_redis_client(app: web.Application): RedisDatabase.USER_NOTIFICATIONS, RedisDatabase.ANNOUNCEMENTS, RedisDatabase.DOCUMENTS, + RedisDatabase.CELERY_TASKS, ) }, settings=redis_settings, @@ -114,6 +115,10 @@ def get_redis_announcements_client(app: web.Application) -> aioredis.Redis: return redis_client +def get_redis_celery_tasks_client_sdk(app: web.Application) -> RedisClientSDK: + return _get_redis_client_sdk(app, RedisDatabase.CELERY_TASKS) + + # PLUGIN SETUP -------------------------------------------------------------------------- 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 5ab341e9c9cc..116ed1f5484a 100644 --- a/services/web/server/src/simcore_service_webserver/storage/_rest.py +++ b/services/web/server/src/simcore_service_webserver/storage/_rest.py @@ -57,7 +57,7 @@ from ..models import AuthenticatedRequestContext, WebServerOwnerMetadata from ..rabbitmq import get_rabbitmq_rpc_client from ..security.decorators import permission_required -from ..tasks._exception_handlers import handle_export_data_exceptions +from ..tasks._controller._rest_exceptions import handle_rest_requests_exceptions from .schemas import StorageFileIDStr from .settings import StorageSettings, get_plugin_settings @@ -487,7 +487,7 @@ class _PathParams(BaseModel): ) @login_required @permission_required("storage.files.*") -@handle_export_data_exceptions +@handle_rest_requests_exceptions async def export_data(request: web.Request) -> web.Response: class _PathParams(BaseModel): location_id: LocationID diff --git a/services/web/server/src/simcore_service_webserver/tasks/_controller/__init__.py b/services/web/server/src/simcore_service_webserver/tasks/_controller/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/services/web/server/src/simcore_service_webserver/tasks/_rest.py b/services/web/server/src/simcore_service_webserver/tasks/_controller/_rest.py similarity index 57% rename from services/web/server/src/simcore_service_webserver/tasks/_rest.py rename to services/web/server/src/simcore_service_webserver/tasks/_controller/_rest.py index 3ab13730940e..e1d6f4010f7b 100644 --- a/services/web/server/src/simcore_service_webserver/tasks/_rest.py +++ b/services/web/server/src/simcore_service_webserver/tasks/_controller/_rest.py @@ -1,11 +1,5 @@ -"""Handlers exposed by storage subsystem - -Mostly resolves and redirect to storage API -""" - import logging from typing import Final -from uuid import UUID from aiohttp import web from models_library.api_schemas_long_running_tasks.base import TaskProgress @@ -14,11 +8,6 @@ TaskResult, TaskStatus, ) -from models_library.api_schemas_rpc_async_jobs.async_jobs import ( - AsyncJobId, -) -from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE -from pydantic import BaseModel from servicelib.aiohttp import status from servicelib.aiohttp.long_running_tasks.server import ( get_long_running_manager, @@ -26,18 +15,20 @@ from servicelib.aiohttp.requests_validation import ( parse_request_path_parameters_as, ) -from servicelib.aiohttp.rest_responses import create_data_response +from servicelib.aiohttp.rest_responses import ( + create_data_response, +) from servicelib.celery.models import OwnerMetadata from servicelib.long_running_tasks import lrt_api -from servicelib.rabbitmq.rpc_interfaces.async_jobs import async_jobs -from .._meta import API_VTAG -from ..login.decorators import login_required -from ..long_running_tasks.plugin import webserver_request_context_decorator -from ..models import AuthenticatedRequestContext, WebServerOwnerMetadata -from ..rabbitmq import get_rabbitmq_rpc_client -from ..security.decorators import permission_required -from ._exception_handlers import handle_export_data_exceptions +from ..._meta import API_VTAG +from ...celery import get_task_manager +from ...login.decorators import login_required +from ...long_running_tasks.plugin import webserver_request_context_decorator +from ...models import AuthenticatedRequestContext, WebServerOwnerMetadata +from .. import _tasks_service +from ._rest_exceptions import handle_rest_requests_exceptions +from ._rest_schemas import TaskPathParams log = logging.getLogger(__name__) @@ -52,8 +43,7 @@ name="get_async_jobs", ) @login_required -@permission_required("storage.files.*") -@handle_export_data_exceptions +@handle_rest_requests_exceptions @webserver_request_context_decorator async def get_async_jobs(request: web.Request) -> web.Response: inprocess_long_running_manager = get_long_running_manager(request.app) @@ -65,11 +55,8 @@ async def get_async_jobs(request: web.Request) -> web.Response: _req_ctx = AuthenticatedRequestContext.model_validate(request) - rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) - - user_async_jobs = await async_jobs.list_jobs( - rabbitmq_rpc_client=rabbitmq_rpc_client, - rpc_namespace=STORAGE_RPC_NAMESPACE, + tasks = await _tasks_service.list_tasks( + get_task_manager(request.app), owner_metadata=OwnerMetadata.model_validate( WebServerOwnerMetadata( user_id=_req_ctx.user_id, @@ -77,16 +64,17 @@ async def get_async_jobs(request: web.Request) -> web.Response: ).model_dump() ), ) + return create_data_response( [ TaskGet( - task_id=f"{job.job_id}", - task_name=job.job_name, - status_href=f"{request.url.with_path(str(request.app.router['get_async_job_status'].url_for(task_id=str(job.job_id))))}", - abort_href=f"{request.url.with_path(str(request.app.router['cancel_async_job'].url_for(task_id=str(job.job_id))))}", - result_href=f"{request.url.with_path(str(request.app.router['get_async_job_result'].url_for(task_id=str(job.job_id))))}", + task_id=f"{task.job_id}", + task_name=task.job_name, + status_href=f"{request.url.with_path(str(request.app.router['get_async_job_status'].url_for(task_id=str(task.job_id))))}", + abort_href=f"{request.url.with_path(str(request.app.router['cancel_async_job'].url_for(task_id=str(task.job_id))))}", + result_href=f"{request.url.with_path(str(request.app.router['get_async_job_result'].url_for(task_id=str(task.job_id))))}", ) - for job in user_async_jobs + for task in tasks ] + [ TaskGet( @@ -101,40 +89,35 @@ async def get_async_jobs(request: web.Request) -> web.Response: ) -class _StorageAsyncJobId(BaseModel): - task_id: AsyncJobId - - @routes.get( _task_prefix + "/{task_id}", name="get_async_job_status", ) @login_required -@handle_export_data_exceptions +@handle_rest_requests_exceptions async def get_async_job_status(request: web.Request) -> web.Response: _req_ctx = AuthenticatedRequestContext.model_validate(request) - rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) + _path_params = parse_request_path_parameters_as(TaskPathParams, request) - async_job_get = parse_request_path_parameters_as(_StorageAsyncJobId, request) - async_job_rpc_status = await async_jobs.status( - rabbitmq_rpc_client=rabbitmq_rpc_client, - rpc_namespace=STORAGE_RPC_NAMESPACE, - job_id=async_job_get.task_id, + task_status = await _tasks_service.get_task_status( + get_task_manager(request.app), owner_metadata=OwnerMetadata.model_validate( WebServerOwnerMetadata( user_id=_req_ctx.user_id, product_name=_req_ctx.product_name, ).model_dump() ), + task_uuid=_path_params.task_id, ) - _task_id = f"{async_job_rpc_status.job_id}" + + _task_id = f"{task_status.job_id}" return create_data_response( TaskStatus( task_progress=TaskProgress( - task_id=_task_id, percent=async_job_rpc_status.progress.percent_value + task_id=_task_id, percent=task_status.progress.percent_value ), - done=async_job_rpc_status.done, + done=task_status.done, started=None, ), status=status.HTTP_200_OK, @@ -146,25 +129,21 @@ async def get_async_job_status(request: web.Request) -> web.Response: name="cancel_async_job", ) @login_required -@permission_required("storage.files.*") -@handle_export_data_exceptions +@handle_rest_requests_exceptions async def cancel_async_job(request: web.Request) -> web.Response: _req_ctx = AuthenticatedRequestContext.model_validate(request) + _path_params = parse_request_path_parameters_as(TaskPathParams, request) - rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) - async_job_get = parse_request_path_parameters_as(_StorageAsyncJobId, request) - - await async_jobs.cancel( - rabbitmq_rpc_client=rabbitmq_rpc_client, - rpc_namespace=STORAGE_RPC_NAMESPACE, - job_id=async_job_get.task_id, + await _tasks_service.cancel_task( + get_task_manager(request.app), owner_metadata=OwnerMetadata.model_validate( WebServerOwnerMetadata( user_id=_req_ctx.user_id, product_name=_req_ctx.product_name, ).model_dump() ), + task_uuid=_path_params.task_id, ) return web.Response(status=status.HTTP_204_NO_CONTENT) @@ -175,29 +154,24 @@ async def cancel_async_job(request: web.Request) -> web.Response: name="get_async_job_result", ) @login_required -@permission_required("storage.files.*") -@handle_export_data_exceptions +@handle_rest_requests_exceptions async def get_async_job_result(request: web.Request) -> web.Response: - class _PathParams(BaseModel): - task_id: UUID _req_ctx = AuthenticatedRequestContext.model_validate(request) + _path_params = parse_request_path_parameters_as(TaskPathParams, request) - rabbitmq_rpc_client = get_rabbitmq_rpc_client(request.app) - async_job_get = parse_request_path_parameters_as(_PathParams, request) - async_job_rpc_result = await async_jobs.result( - rabbitmq_rpc_client=rabbitmq_rpc_client, - rpc_namespace=STORAGE_RPC_NAMESPACE, - job_id=async_job_get.task_id, + task_result = await _tasks_service.get_task_result( + get_task_manager(request.app), owner_metadata=OwnerMetadata.model_validate( WebServerOwnerMetadata( user_id=_req_ctx.user_id, product_name=_req_ctx.product_name, ).model_dump() ), + task_uuid=_path_params.task_id, ) return create_data_response( - TaskResult(result=async_job_rpc_result.result, error=None), + TaskResult(result=task_result.result, error=None), status=status.HTTP_200_OK, ) diff --git a/services/web/server/src/simcore_service_webserver/tasks/_exception_handlers.py b/services/web/server/src/simcore_service_webserver/tasks/_controller/_rest_exceptions.py similarity index 95% rename from services/web/server/src/simcore_service_webserver/tasks/_exception_handlers.py rename to services/web/server/src/simcore_service_webserver/tasks/_controller/_rest_exceptions.py index 81b9806c57a9..3a170aab8f45 100644 --- a/services/web/server/src/simcore_service_webserver/tasks/_exception_handlers.py +++ b/services/web/server/src/simcore_service_webserver/tasks/_controller/_rest_exceptions.py @@ -13,7 +13,7 @@ ) from servicelib.aiohttp import status -from ..exception_handling import ( +from ...exception_handling import ( ExceptionToHttpErrorMap, HttpErrorInfo, exception_handling_decorator, @@ -69,6 +69,6 @@ } -handle_export_data_exceptions = exception_handling_decorator( +handle_rest_requests_exceptions = exception_handling_decorator( to_exceptions_handlers_map(_TO_HTTP_ERROR_MAP) ) diff --git a/services/web/server/src/simcore_service_webserver/tasks/_controller/_rest_schemas.py b/services/web/server/src/simcore_service_webserver/tasks/_controller/_rest_schemas.py new file mode 100644 index 000000000000..7f25cfca51da --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/tasks/_controller/_rest_schemas.py @@ -0,0 +1,8 @@ +from uuid import UUID + +from pydantic import BaseModel, ConfigDict + + +class TaskPathParams(BaseModel): + task_id: UUID + model_config = ConfigDict(extra="forbid", frozen=True) diff --git a/services/web/server/src/simcore_service_webserver/tasks/_tasks_service.py b/services/web/server/src/simcore_service_webserver/tasks/_tasks_service.py new file mode 100644 index 000000000000..36c5c382b255 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/tasks/_tasks_service.py @@ -0,0 +1,128 @@ +import logging + +from celery_library.errors import ( + TaskManagerError, + TaskNotFoundError, + TransferrableCeleryError, + decode_celery_transferrable_error, +) +from models_library.api_schemas_rpc_async_jobs.async_jobs import ( + AsyncJobGet, + AsyncJobResult, + AsyncJobStatus, +) +from models_library.api_schemas_rpc_async_jobs.exceptions import ( + JobError, + JobMissingError, + JobNotDoneError, + JobSchedulerError, +) +from servicelib.celery.models import ( + OwnerMetadata, + TaskState, + TaskUUID, +) +from servicelib.celery.task_manager import TaskManager +from servicelib.logging_utils import log_catch + +_logger = logging.getLogger(__name__) + + +async def cancel_task( + task_manager: TaskManager, + *, + owner_metadata: OwnerMetadata, + task_uuid: TaskUUID, +): + try: + await task_manager.cancel_task( + owner_metadata=owner_metadata, + task_uuid=task_uuid, + ) + except TaskNotFoundError as exc: + raise JobMissingError(job_id=task_uuid) from exc + except TaskManagerError as exc: + raise JobSchedulerError(exc=f"{exc}") from exc + + +async def get_task_result( + task_manager: TaskManager, + *, + owner_metadata: OwnerMetadata, + task_uuid: TaskUUID, +) -> AsyncJobResult: + try: + _status = await task_manager.get_task_status( + owner_metadata=owner_metadata, + task_uuid=task_uuid, + ) + if not _status.is_done: + raise JobNotDoneError(job_id=task_uuid) + _result = await task_manager.get_task_result( + owner_metadata=owner_metadata, + task_uuid=task_uuid, + ) + except TaskNotFoundError as exc: + raise JobMissingError(job_id=task_uuid) from exc + except TaskManagerError as exc: + raise JobSchedulerError(exc=f"{exc}") from exc + + if _status.task_state == TaskState.FAILURE: + # fallback exception to report + exc_type = type(_result).__name__ + exc_msg = f"{_result}" + + # try to recover the original error + exception = None + with log_catch(_logger, reraise=False): + assert isinstance(_result, TransferrableCeleryError) # nosec + exception = decode_celery_transferrable_error(_result) + exc_type = type(exception).__name__ + exc_msg = f"{exception}" + + if exception is None: + _logger.warning("Was not expecting '%s': '%s'", exc_type, exc_msg) + + raise JobError(job_id=task_uuid, exc_type=exc_type, exc_msg=exc_msg) + + return AsyncJobResult(result=_result) + + +async def get_task_status( + task_manager: TaskManager, + *, + owner_metadata: OwnerMetadata, + task_uuid: TaskUUID, +) -> AsyncJobStatus: + try: + task_status = await task_manager.get_task_status( + owner_metadata=owner_metadata, + task_uuid=task_uuid, + ) + except TaskNotFoundError as exc: + raise JobMissingError(job_id=task_uuid) from exc + except TaskManagerError as exc: + raise JobSchedulerError(exc=f"{exc}") from exc + + return AsyncJobStatus( + job_id=task_uuid, + progress=task_status.progress_report, + done=task_status.is_done, + ) + + +async def list_tasks( + task_manager: TaskManager, + *, + owner_metadata: OwnerMetadata, +) -> list[AsyncJobGet]: + try: + tasks = await task_manager.list_tasks( + owner_metadata=owner_metadata, + ) + except TaskManagerError as exc: + raise JobSchedulerError(exc=f"{exc}") from exc + + return [ + AsyncJobGet(job_id=task.uuid, job_name=task.metadata.name) for task in tasks + ] diff --git a/services/web/server/src/simcore_service_webserver/tasks/plugin.py b/services/web/server/src/simcore_service_webserver/tasks/plugin.py index e9bfdeea222e..b12d761b7c72 100644 --- a/services/web/server/src/simcore_service_webserver/tasks/plugin.py +++ b/services/web/server/src/simcore_service_webserver/tasks/plugin.py @@ -1,7 +1,7 @@ from aiohttp import web from ..rest.plugin import setup_rest -from . import _rest +from ._controller import _rest def setup_tasks(app: web.Application): diff --git a/services/web/server/src/simcore_service_webserver/tasks/tasks_service.py b/services/web/server/src/simcore_service_webserver/tasks/tasks_service.py new file mode 100644 index 000000000000..9858764cf586 --- /dev/null +++ b/services/web/server/src/simcore_service_webserver/tasks/tasks_service.py @@ -0,0 +1,14 @@ +from ._tasks_service import ( + cancel_task, + get_task_result, + get_task_status, + list_tasks, +) + +__all__: tuple[str, ...] = ( + "cancel_task", + "get_task_result", + "get_task_status", + "list_tasks", +) +# nopycln: file diff --git a/services/web/server/tests/unit/with_dbs/01/conftest.py b/services/web/server/tests/unit/with_dbs/01/conftest.py index 5e77d24abf3f..96ec01abd06e 100644 --- a/services/web/server/tests/unit/with_dbs/01/conftest.py +++ b/services/web/server/tests/unit/with_dbs/01/conftest.py @@ -2,7 +2,14 @@ # pylint: disable=unused-argument # pylint: disable=unused-variable +from functools import partial +from typing import Any + import pytest +from pytest_mock import MockerFixture, MockType +from pytest_simcore.helpers.typing_mock import HandlerMockFactory +from simcore_service_webserver.storage import _rest +from simcore_service_webserver.tasks import _tasks_service @pytest.fixture @@ -12,3 +19,50 @@ def app_environment( # NOTE: overrides app_environment monkeypatch.setenv("WEBSERVER_GARBAGE_COLLECTOR", "null") return app_environment | {"WEBSERVER_GARBAGE_COLLECTOR": "null"} + + +def _result_or_exception_side_effect(result_or_exception: Any, *args, **kwargs): + if isinstance(result_or_exception, Exception): + raise result_or_exception + + return result_or_exception + + +def _create_handler_mock_factory( + mocker: MockerFixture, module: Any +) -> HandlerMockFactory: + def _create( + handler_name: str, + return_value: Any = None, + exception: Exception | None = None, + side_effect: Any | None = None, + ) -> MockType: + + assert exception is None or side_effect is None + + return mocker.patch.object( + module, + handler_name, + return_value=return_value, + side_effect=( + partial(_result_or_exception_side_effect, side_effect) + if side_effect + else None + ), + ) + + return _create + + +@pytest.fixture() +def mock_handler_in_storage_rest( + mocker: MockerFixture, +) -> HandlerMockFactory: + return _create_handler_mock_factory(mocker, _rest) + + +@pytest.fixture() +def mock_handler_in_task_service( + mocker: MockerFixture, +) -> HandlerMockFactory: + return _create_handler_mock_factory(mocker, _tasks_service) diff --git a/services/web/server/tests/unit/with_dbs/01/storage/test_storage.py b/services/web/server/tests/unit/with_dbs/01/storage/test_storage.py index 835b38f3bdb1..ecbad97651e1 100644 --- a/services/web/server/tests/unit/with_dbs/01/storage/test_storage.py +++ b/services/web/server/tests/unit/with_dbs/01/storage/test_storage.py @@ -24,10 +24,7 @@ AsyncJobStatus, ) from models_library.api_schemas_rpc_async_jobs.exceptions import ( - JobAbortedError, - JobError, JobMissingError, - JobNotDoneError, JobSchedulerError, ) from models_library.api_schemas_storage.export_data_async_jobs import ( @@ -53,15 +50,20 @@ from pydantic import TypeAdapter from pytest_mock import MockerFixture from pytest_simcore.helpers.assert_checks import assert_status +from pytest_simcore.helpers.typing_mock import HandlerMockFactory from pytest_simcore.helpers.webserver_users import UserInfoDict from servicelib.aiohttp import status from servicelib.fastapi.rest_pagination import CustomizedPathsCursorPage -from servicelib.rabbitmq.rpc_interfaces.async_jobs import async_jobs from servicelib.rabbitmq.rpc_interfaces.async_jobs.async_jobs import ( submit, ) from servicelib.rabbitmq.rpc_interfaces.storage.simcore_s3 import start_export_data from simcore_postgres_database.models.users import UserRole +from simcore_service_webserver.tasks._tasks_service import ( + cancel_task, + get_task_result, + get_task_status, +) from yarl import URL API_VERSION = "v0" @@ -422,23 +424,6 @@ async def test_upload_file( assert not data -@pytest.fixture -def create_storage_rpc_client_mock( - mocker: MockerFixture, -) -> Callable[[str, str, Any], None]: - def _(module: str, 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 - - for fct in (f"{module}.{method}",): - mocker.patch(fct, side_effect=side_effect) - - return _ - - @pytest.mark.parametrize("user_role", _user_roles) @pytest.mark.parametrize( "backend_result_or_exception, expected_status", @@ -473,15 +458,14 @@ async def test_export_data( user_role: UserRole, logged_user: UserInfoDict, client: TestClient, - create_storage_rpc_client_mock: Callable[[str, str, Any], None], + mock_handler_in_storage_rest: HandlerMockFactory, faker: Faker, backend_result_or_exception: Any, expected_status: int, ): - create_storage_rpc_client_mock( - "simcore_service_webserver.storage._rest", + mock_handler_in_storage_rest( start_export_data.__name__, - backend_result_or_exception, + side_effect=backend_result_or_exception, ) _body = DataExportPost( @@ -516,15 +500,14 @@ async def test_get_async_jobs_status( user_role: UserRole, logged_user: UserInfoDict, client: TestClient, - create_storage_rpc_client_mock: Callable[[str, str, Any], None], + mock_handler_in_task_service: HandlerMockFactory, backend_result_or_exception: Any, expected_status: int, ): _job_id = AsyncJobId(_faker.uuid4()) - create_storage_rpc_client_mock( - "simcore_service_webserver.tasks._rest", - f"async_jobs.{async_jobs.status.__name__}", - backend_result_or_exception, + mock_handler_in_task_service( + get_task_status.__name__, + side_effect=backend_result_or_exception, ) response = await client.get(f"/{API_VERSION}/tasks/{_job_id}") @@ -553,92 +536,21 @@ async def test_cancel_async_jobs( user_role: UserRole, logged_user: UserInfoDict, client: TestClient, - create_storage_rpc_client_mock: Callable[[str, str, Any], None], + mock_handler_in_task_service: HandlerMockFactory, faker: Faker, backend_result_or_exception: Any, expected_status: int, ): _job_id = AsyncJobId(faker.uuid4()) - create_storage_rpc_client_mock( - "simcore_service_webserver.tasks._rest", - f"async_jobs.{async_jobs.cancel.__name__}", - backend_result_or_exception, + mock_handler_in_task_service( + cancel_task.__name__, + side_effect=backend_result_or_exception, ) response = await client.delete(f"/{API_VERSION}/tasks/{_job_id}") assert response.status == expected_status -@pytest.mark.parametrize("user_role", _user_roles) -@pytest.mark.parametrize( - "backend_result_or_exception, expected_status", - [ - (JobNotDoneError(job_id=_faker.uuid4()), status.HTTP_404_NOT_FOUND), - (AsyncJobResult(result=None), status.HTTP_200_OK), - (JobError(job_id=_faker.uuid4()), status.HTTP_500_INTERNAL_SERVER_ERROR), - (JobAbortedError(job_id=_faker.uuid4()), status.HTTP_410_GONE), - (JobSchedulerError(exc=_faker.text()), status.HTTP_500_INTERNAL_SERVER_ERROR), - (JobMissingError(job_id=_faker.uuid4()), status.HTTP_404_NOT_FOUND), - ], - 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, str, Any], None], - faker: Faker, - backend_result_or_exception: Any, - expected_status: int, -): - _job_id = AsyncJobId(faker.uuid4()) - create_storage_rpc_client_mock( - "simcore_service_webserver.tasks._rest", - f"async_jobs.{async_jobs.result.__name__}", - backend_result_or_exception, - ) - - response = await client.get(f"/{API_VERSION}/tasks/{_job_id}/result") - assert response.status == expected_status - - -@pytest.mark.parametrize("user_role", _user_roles) -@pytest.mark.parametrize( - "backend_result_or_exception, expected_status", - [ - ( - [ - AsyncJobGet( - job_id=AsyncJobId(_faker.uuid4()), - job_name="task_name", - ) - ], - status.HTTP_200_OK, - ), - (JobSchedulerError(exc=_faker.text()), status.HTTP_500_INTERNAL_SERVER_ERROR), - ], - 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, str, Any], None], - backend_result_or_exception: Any, - expected_status: int, -): - create_storage_rpc_client_mock( - "simcore_service_webserver.tasks._rest", - f"async_jobs.{async_jobs.list_jobs.__name__}", - backend_result_or_exception, - ) - - response = await client.get(f"/{API_VERSION}/tasks") - assert response.status == expected_status - if response.status == status.HTTP_200_OK: - Envelope[list[TaskGet]].model_validate(await response.json()) - - @pytest.mark.parametrize("user_role", _user_roles) @pytest.mark.parametrize( "http_method, href, backend_method, backend_object, return_status, return_schema", @@ -646,7 +558,7 @@ async def test_get_user_async_jobs( ( "GET", "status_href", - async_jobs.status.__name__, + get_task_status.__name__, AsyncJobStatus( job_id=AsyncJobId(_faker.uuid4()), progress=ProgressReport(actual_value=0.5, total=1.0), @@ -658,7 +570,7 @@ async def test_get_user_async_jobs( ( "DELETE", "abort_href", - async_jobs.cancel.__name__, + cancel_task.__name__, AsyncJobAbort(result=True, job_id=AsyncJobId(_faker.uuid4())), status.HTTP_204_NO_CONTENT, None, @@ -666,7 +578,7 @@ async def test_get_user_async_jobs( ( "GET", "result_href", - async_jobs.result.__name__, + get_task_result.__name__, AsyncJobResult(result=None), status.HTTP_200_OK, TaskResult, @@ -677,7 +589,8 @@ async def test_get_async_job_links( user_role: UserRole, logged_user: UserInfoDict, client: TestClient, - create_storage_rpc_client_mock: Callable[[str, str, Any], None], + mock_handler_in_task_service: HandlerMockFactory, + mock_handler_in_storage_rest: HandlerMockFactory, faker: Faker, http_method: str, href: str, @@ -686,10 +599,9 @@ async def test_get_async_job_links( return_status: int, return_schema: OutputSchema | None, ): - create_storage_rpc_client_mock( - "simcore_service_webserver.storage._rest", + mock_handler_in_storage_rest( start_export_data.__name__, - ( + return_value=( AsyncJobGet( job_id=AsyncJobId(f"{_faker.uuid4()}"), job_name="export_data", @@ -709,10 +621,9 @@ async def test_get_async_job_links( assert response_body_data is not None # Call the different links and check the correct model and return status - create_storage_rpc_client_mock( - "simcore_service_webserver.tasks._rest", - f"async_jobs.{backend_method}", - backend_object, + mock_handler_in_task_service( + backend_method, + return_value=backend_object, ) response = await client.request( http_method, URL(getattr(response_body_data, href)).path diff --git a/services/web/server/tests/unit/with_dbs/01/tasks/test_tasks_rest_controller.py b/services/web/server/tests/unit/with_dbs/01/tasks/test_tasks_rest_controller.py new file mode 100644 index 000000000000..fcb230ddc0f2 --- /dev/null +++ b/services/web/server/tests/unit/with_dbs/01/tasks/test_tasks_rest_controller.py @@ -0,0 +1,180 @@ +# pylint: disable=unused-argument + +from collections.abc import Callable +from typing import Any, Final + +import pytest +from aiohttp.test_utils import TestClient +from common_library.users_enums import UserRole +from faker import Faker +from models_library.api_schemas_long_running_tasks.tasks import ( + TaskGet, + TaskStatus, +) +from models_library.api_schemas_rpc_async_jobs.async_jobs import ( + AsyncJobGet, + AsyncJobId, + AsyncJobResult, + AsyncJobStatus, +) +from models_library.api_schemas_rpc_async_jobs.exceptions import ( + JobAbortedError, + JobError, + JobMissingError, + JobNotDoneError, + JobSchedulerError, +) +from models_library.generics import Envelope +from models_library.progress_bar import ProgressReport +from pytest_mock import MockerFixture +from pytest_simcore.helpers.typing_mock import HandlerMockFactory +from servicelib.aiohttp import status +from simcore_service_webserver.tasks import _tasks_service +from simcore_service_webserver.tasks._controller import _rest + +API_VERSION = "v0" + + +PREFIX = "/" + API_VERSION + "/tasks" +_faker = Faker() +_user_roles: Final[list[UserRole]] = [ + UserRole.GUEST, + UserRole.USER, + UserRole.TESTER, + UserRole.PRODUCT_OWNER, + UserRole.ADMIN, +] + + +@pytest.fixture(name="create_consume_events_mock") +def create_consume_events_mock_fixture( + mocker: MockerFixture, +) -> Callable[[Any], None]: + def _(result_or_exception: Any): + async def mock_consume_events(*args, **kwargs): + if isinstance(result_or_exception, Exception): + raise result_or_exception + # Yield the mock events + for event_id, event_data in result_or_exception: + yield event_id, event_data + + mock_task_manager = mocker.MagicMock() + mock_task_manager.consume_task_events = mock_consume_events + mocker.patch.object( + _rest, + "get_task_manager", + return_value=mock_task_manager, + ) + + return _ + + +class MockEvent: + def __init__(self, event_type: str, event_data: dict[str, Any]): + self.type = event_type + self.data = event_data + + +@pytest.mark.parametrize("user_role", _user_roles) +@pytest.mark.parametrize( + "backend_result_or_exception, expected_status", + [ + ( + [ + AsyncJobGet( + job_id=AsyncJobId(_faker.uuid4()), + job_name="task_name", + ) + ], + status.HTTP_200_OK, + ), + (JobSchedulerError(exc=_faker.text()), status.HTTP_500_INTERNAL_SERVER_ERROR), + ], + ids=lambda x: type(x).__name__, +) +@pytest.mark.usefixtures("user_role", "logged_user") +async def test_get_user_async_jobs( + client: TestClient, + mock_handler_in_task_service: HandlerMockFactory, + backend_result_or_exception: Any, + expected_status: int, +): + mock_handler_in_task_service( + _tasks_service.list_tasks.__name__, + side_effect=backend_result_or_exception, + ) + + response = await client.get(f"/{API_VERSION}/tasks") + assert response.status == expected_status + if response.status == status.HTTP_200_OK: + Envelope[list[TaskGet]].model_validate(await response.json()) + + +@pytest.mark.parametrize("user_role", _user_roles) +@pytest.mark.parametrize( + "backend_result_or_exception, expected_status", + [ + ( + AsyncJobStatus( + job_id=AsyncJobId(f"{_faker.uuid4()}"), + progress=ProgressReport(actual_value=0.5, total=1.0), + done=False, + ), + status.HTTP_200_OK, + ), + (JobSchedulerError(exc=_faker.text()), status.HTTP_500_INTERNAL_SERVER_ERROR), + (JobMissingError(job_id=_faker.uuid4()), status.HTTP_404_NOT_FOUND), + ], + ids=lambda x: type(x).__name__, +) +@pytest.mark.usefixtures("user_role", "logged_user") +async def test_get_async_jobs_status( + client: TestClient, + mock_handler_in_task_service: HandlerMockFactory, + backend_result_or_exception: Any, + expected_status: int, +): + _job_id = AsyncJobId(_faker.uuid4()) + mock_handler_in_task_service( + _tasks_service.get_task_status.__name__, + side_effect=backend_result_or_exception, + ) + + response = await client.get(f"/{API_VERSION}/tasks/{_job_id}") + assert response.status == expected_status + if response.status == status.HTTP_200_OK: + response_body_data = ( + Envelope[TaskStatus].model_validate(await response.json()).data + ) + assert response_body_data is not None + + +@pytest.mark.parametrize("user_role", _user_roles) +@pytest.mark.parametrize( + "backend_result_or_exception, expected_status", + [ + (JobNotDoneError(job_id=_faker.uuid4()), status.HTTP_404_NOT_FOUND), + (AsyncJobResult(result=None), status.HTTP_200_OK), + (JobError(job_id=_faker.uuid4()), status.HTTP_500_INTERNAL_SERVER_ERROR), + (JobAbortedError(job_id=_faker.uuid4()), status.HTTP_410_GONE), + (JobSchedulerError(exc=_faker.text()), status.HTTP_500_INTERNAL_SERVER_ERROR), + (JobMissingError(job_id=_faker.uuid4()), status.HTTP_404_NOT_FOUND), + ], + ids=lambda x: type(x).__name__, +) +@pytest.mark.usefixtures("user_role", "logged_user") +async def test_get_async_job_result( + client: TestClient, + mock_handler_in_task_service: HandlerMockFactory, + faker: Faker, + backend_result_or_exception: Any, + expected_status: int, +): + _job_id = AsyncJobId(faker.uuid4()) + mock_handler_in_task_service( + _tasks_service.get_task_result.__name__, + side_effect=backend_result_or_exception, + ) + + response = await client.get(f"/{API_VERSION}/tasks/{_job_id}/result") + assert response.status == expected_status diff --git a/services/web/server/tests/unit/with_dbs/01/test_long_running_tasks.py b/services/web/server/tests/unit/with_dbs/01/test_long_running_tasks.py index 7a06060369e2..08bbd2aa2625 100644 --- a/services/web/server/tests/unit/with_dbs/01/test_long_running_tasks.py +++ b/services/web/server/tests/unit/with_dbs/01/test_long_running_tasks.py @@ -1,3 +1,4 @@ +# pylint: disable=protected-access # pylint: disable=redefined-outer-name # pylint: disable=too-many-arguments # pylint: disable=unused-argument @@ -9,6 +10,7 @@ from unittest.mock import Mock import pytest +import simcore_service_webserver.tasks._controller._rest from aiohttp.test_utils import TestClient from faker import Faker from pytest_mock import MockerFixture @@ -83,8 +85,9 @@ async def list_tasks(self, *args, **kwargs): mock = Mock() mock.tasks_manager = _DummyTaskManager() - mocker.patch( - "servicelib.aiohttp.long_running_tasks._routes.get_long_running_manager", + mocker.patch.object( + simcore_service_webserver.tasks._controller._rest, + "get_long_running_manager", return_value=mock, ) diff --git a/services/web/server/tests/unit/with_dbs/03/test_login_auth_app.py b/services/web/server/tests/unit/with_dbs/03/test_login_auth_app.py index 8f80e463063c..91ec964e1f50 100644 --- a/services/web/server/tests/unit/with_dbs/03/test_login_auth_app.py +++ b/services/web/server/tests/unit/with_dbs/03/test_login_auth_app.py @@ -67,6 +67,7 @@ def app_environment_for_wb_authz_service_dict( "POSTGRES_USER": postgres_cfg["user"], "POSTGRES_PASSWORD": postgres_cfg["password"], "HOSTNAME": docker_compose_service_hostname, + "WEBSERVER_CELERY": "null", "WEBSERVER_TRACING": "null", # BUT we will disable it for tests }