Skip to content
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
156a19a
factor out tasks part in storage rpc api
bisgaard-itis Feb 14, 2025
64c137d
use 'async_jobs' terminology and refactor rpc client for reusability
bisgaard-itis Feb 14, 2025
f6e73e3
further refactoring
bisgaard-itis Feb 14, 2025
50fa599
make async job rpc client more reusable
bisgaard-itis Feb 14, 2025
1ce7158
tasks -> async jobs
bisgaard-itis Feb 15, 2025
1229e92
tasks -> async_job
bisgaard-itis Feb 15, 2025
ce064ce
start adding data-export endpoints in webserver
bisgaard-itis Feb 15, 2025
223631e
further renaming
bisgaard-itis Feb 16, 2025
0bd94d6
create openapi-specs make target for webserver to ensure github workf…
bisgaard-itis Feb 16, 2025
b43b22d
add webserver endpoint for triggering data export
bisgaard-itis Feb 16, 2025
9795838
add webserver endpoint for getting async job status
bisgaard-itis Feb 16, 2025
224dfe2
minor fix
bisgaard-itis Feb 17, 2025
3fb022e
add initial test
bisgaard-itis Feb 17, 2025
ac06db4
pass login and permission in test
bisgaard-itis Feb 17, 2025
249967e
add get status test
bisgaard-itis Feb 17, 2025
8b154af
add location id in webserver endpoint
bisgaard-itis Feb 17, 2025
ac20937
task_id -> job_id
bisgaard-itis Feb 17, 2025
527e7a6
cleanup
bisgaard-itis Feb 17, 2025
999907e
add test for abort endpoint
bisgaard-itis Feb 17, 2025
520c043
add test for endpoint of getting result
bisgaard-itis Feb 17, 2025
1b42a8f
update webserver openapi specs
bisgaard-itis Feb 17, 2025
a649c71
start adding exceptions and propagating them
bisgaard-itis Feb 17, 2025
4ad6aac
add exception handling to endpoint for triggering data export
bisgaard-itis Feb 18, 2025
33b5ccf
remove 'data_export' from async jobs model lib section
bisgaard-itis Feb 18, 2025
661b3b2
add exception handling for remaining endpoints
bisgaard-itis Feb 18, 2025
6972cf5
merge master into 7197-add-zipping-endpoints-in-storage
bisgaard-itis Feb 18, 2025
e19eea7
update openapi specs of webserver
bisgaard-itis Feb 18, 2025
311ba29
add user id to data export endpoint
bisgaard-itis Feb 18, 2025
d614e7e
fix typecheck
bisgaard-itis Feb 18, 2025
db55e79
fix openapi spec test
bisgaard-itis Feb 18, 2025
8bd254d
merge master into 7197-add-zipping-endpoints-in-storage
bisgaard-itis Feb 18, 2025
1718df8
@sanderegg fix absolute imports
bisgaard-itis Feb 18, 2025
7faf55f
@sanderegg @pcrespov remove 'rpc' from classnames
bisgaard-itis Feb 18, 2025
434602f
task_progress -> progress
bisgaard-itis Feb 18, 2025
fd777ab
rename storage schemas exposed via webserver
bisgaard-itis Feb 18, 2025
400a6c1
use ProgressReport
bisgaard-itis Feb 18, 2025
3383265
restructuring
bisgaard-itis Feb 18, 2025
b0f8a7f
services/webserver api version: 0.58.0 → 0.59.0
bisgaard-itis Feb 18, 2025
8acc41c
storage/_handlers.py -> storage/_rest.py
bisgaard-itis Feb 18, 2025
df04839
merge master into 7197-add-zipping-endpoints-in-storage
bisgaard-itis Feb 18, 2025
c3758bd
update opena api specs
bisgaard-itis Feb 18, 2025
ccff359
@pcrespov streamline to_rpc_schema methods
bisgaard-itis Feb 19, 2025
09a3650
merge master into 7197-add-zipping-endpoints-in-storage
bisgaard-itis Feb 19, 2025
b01e479
add rpc method for getting jobs associated with user
bisgaard-itis Feb 19, 2025
eec16d8
add webserver rest endpoint for getting user jobs
bisgaard-itis Feb 19, 2025
432dbb3
add test of rest endpoint for getting jobs associated with user
bisgaard-itis Feb 19, 2025
8e6162d
minor cleanup
bisgaard-itis Feb 19, 2025
4dfe2d0
merge master into 7197-add-zipping-endpoints-in-storage
bisgaard-itis Feb 19, 2025
f817a52
move thing around
bisgaard-itis Feb 19, 2025
6a19215
make pylint happy
bisgaard-itis Feb 19, 2025
8356158
update openapi specs
bisgaard-itis Feb 19, 2025
da0f6ea
fix webserver mocks
bisgaard-itis Feb 19, 2025
63bc45c
cleanup
bisgaard-itis Feb 19, 2025
8821e57
merge master into 7197-add-zipping-endpoints-in-storage
bisgaard-itis Feb 19, 2025
e5ff440
make pylint happy
bisgaard-itis Feb 19, 2025
f0848a4
export get async jobs method in webserver openapi specs
bisgaard-itis Feb 19, 2025
98da41b
Update api/specs/web-server/_storage.py
bisgaard-itis Feb 19, 2025
83a8941
Update api/specs/web-server/_storage.py
bisgaard-itis Feb 19, 2025
bc49fb0
Update api/specs/web-server/_storage.py
bisgaard-itis Feb 19, 2025
e1488a8
Update api/specs/web-server/_storage.py
bisgaard-itis Feb 19, 2025
247d64f
Update api/specs/web-server/_storage.py
bisgaard-itis Feb 19, 2025
61d0683
use AsyncJobId consistently @GitHK
bisgaard-itis Feb 19, 2025
6132f82
update openapi specs
bisgaard-itis Feb 19, 2025
e50c1aa
make list jobs endpoint generic
bisgaard-itis Feb 19, 2025
57e2576
propagate changes to webserver
bisgaard-itis Feb 19, 2025
25265cb
make pylint happy
bisgaard-itis Feb 19, 2025
dd3fdf9
make submit job endpoint generic @GitHK
bisgaard-itis Feb 20, 2025
fe8f33e
factor out RequestContext and forward product name to storage @matusd…
bisgaard-itis Feb 20, 2025
666ed11
merge master into 7197-add-zipping-endpoints-in-storage
bisgaard-itis Feb 20, 2025
38030ab
fix test in storage
bisgaard-itis Feb 20, 2025
2be7088
merge master into 7197-add-zipping-endpoints-in-storage
bisgaard-itis Feb 20, 2025
63ccfa2
Merge branch 'master' into 7197-add-zipping-endpoints-in-storage
bisgaard-itis Feb 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions api/specs/web-server/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ install-dev install: _check_venv_active
.PHONY: all
all: _check_venv_active install
python openapi.py

.PHONY: openapi-specs
openapi-specs: all
23 changes: 23 additions & 0 deletions api/specs/web-server/_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
from models_library.generics import Envelope
from models_library.projects_nodes_io import LocationID
from models_library.storage_schemas import (
AsyncJobGet,
AsyncJobStatus,
DataExportPost,
FileLocation,
FileMetaDataGet,
FileUploadCompleteFutureResponse,
Expand Down Expand Up @@ -167,3 +170,23 @@ async def is_completed_upload_file(
location_id: LocationID, file_id: StorageFileIDStr, future_id: str
):
"""Returns state of upload completion"""


# data export
@router.post(
"/storage/export-data",
response_model=Envelope[AsyncJobGet],
name="storage_export_data",
description="Export data",
)
async def export_data(data_export: DataExportPost):
"""Trigger data export. Returns async job id for getting status and results"""


@router.get(
"/storage/async-jobs/status",
response_model=Envelope[AsyncJobStatus],
name="storage_async_job_status",
)
async def get_async_job_status(task_id: AsyncJobGet):
"""Get async job status"""
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
from pydantic import BaseModel, Field, PositiveFloat, model_validator
from typing_extensions import Self

TaskRpcId: TypeAlias = UUID
AsyncJobRpcId: TypeAlias = UUID


class TaskRpcStatus(BaseModel):
task_id: TaskRpcId
class AsyncJobRpcStatus(BaseModel):
job_id: AsyncJobRpcId
task_progress: PositiveFloat = Field(..., ge=0.0, le=1.0)
done: bool
started: datetime
Expand All @@ -26,11 +26,16 @@ def _check_consistency(self) -> Self:
return self


class TaskRpcResult(BaseModel):
class AsyncJobRpcResult(BaseModel):
result: Any | None
error: Any | None


class TaskRpcGet(BaseModel):
task_id: TaskRpcId
class AsyncJobRpcGet(BaseModel):
job_id: AsyncJobRpcId
task_name: str


class AsyncJobRpcAbort(BaseModel):
result: bool
job_id: AsyncJobRpcId
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from common_library.errors_classes import OsparcErrorMixin


class BaseAsyncjobRpcError(OsparcErrorMixin, RuntimeError):
pass


class StatusError(BaseAsyncjobRpcError):
msg_template: str = "Could not get status of job {job_id}"


class ResultError(BaseAsyncjobRpcError):
msg_template: str = "Could not get results of job {job_id}"
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# pylint: disable=R6301
from pathlib import Path

from common_library.errors_classes import OsparcErrorMixin
from models_library.projects_nodes_io import LocationID
from models_library.users import UserID
from pydantic import BaseModel, Field


class DataExportTaskStartInput(BaseModel):
user_id: UserID
location_id: LocationID
paths: list[Path] = Field(..., min_length=1)


### Exceptions


class StorageRpcErrors(OsparcErrorMixin, RuntimeError):
pass


class InvalidFileIdentifierError(StorageRpcErrors):
msg_template: str = "Could not find the file {file_id}"


class AccessRightError(StorageRpcErrors):
msg_template: str = "User {user_id} does not have access to file {file_id}"


class DataExportError(StorageRpcErrors):
msg_template: str = "Could not complete data export job with id {job_id}"

This file was deleted.

64 changes: 64 additions & 0 deletions packages/models-library/src/models_library/storage_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,28 @@

from datetime import datetime
from enum import Enum

# /data-export
from pathlib import Path
from typing import Annotated, Any, Literal, Self, TypeAlias
from uuid import UUID

from models_library.api_schemas_rpc_async_jobs.async_jobs import (
AsyncJobRpcGet,
AsyncJobRpcResult,
AsyncJobRpcStatus,
)
from models_library.api_schemas_storage.data_export_async_jobs import (
DataExportTaskStartInput,
)
from models_library.projects import ProjectID
from models_library.users import UserID
from pydantic import (
BaseModel,
ByteSize,
ConfigDict,
Field,
PositiveFloat,
PositiveInt,
RootModel,
StringConstraints,
Expand Down Expand Up @@ -368,3 +380,55 @@ def ensure_consistent_entries(self: Self) -> Self:

class SoftCopyBody(BaseModel):
link_id: SimcoreS3FileID


class DataExportPost(BaseModel):
paths: list[Path]

def to_storage_model(
self, user_id: UserID, location_id: LocationID
) -> DataExportTaskStartInput:
return DataExportTaskStartInput(
paths=self.paths, user_id=user_id, location_id=location_id
)


class AsyncJobGet(BaseModel):
job_id: UUID

@classmethod
def from_async_job_rpc_get(cls, async_job_rpc_get: AsyncJobRpcGet) -> "AsyncJobGet":
return AsyncJobGet(job_id=async_job_rpc_get.job_id)


class AsyncJobStatus(BaseModel):
job_id: UUID
task_progress: PositiveFloat = Field(..., ge=0.0, le=1.0)
done: bool
started: datetime
stopped: datetime | None

@classmethod
def from_async_job_rpc_status(
cls, async_job_rpc_status: AsyncJobRpcStatus
) -> "AsyncJobStatus":
return AsyncJobStatus(
job_id=async_job_rpc_status.job_id,
task_progress=async_job_rpc_status.task_progress,
done=async_job_rpc_status.done,
started=async_job_rpc_status.started,
stopped=async_job_rpc_status.stopped,
)


class AsyncJobResult(BaseModel):
result: Any | None
error: Any | None

@classmethod
def from_async_job_rpc_result(
cls, async_job_rpc_result: AsyncJobRpcResult
) -> "AsyncJobResult":
return AsyncJobResult(
result=async_job_rpc_result.result, error=async_job_rpc_result.error
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from typing import Final

from models_library.api_schemas_rpc_async_jobs.async_jobs import (
AsyncJobRpcAbort,
AsyncJobRpcId,
AsyncJobRpcResult,
AsyncJobRpcStatus,
)
from models_library.rabbitmq_basic_types import RPCMethodName, RPCNamespace
from pydantic import NonNegativeInt, TypeAdapter

from ... import RabbitMQRPCClient

_DEFAULT_TIMEOUT_S: Final[NonNegativeInt] = 30

_RPC_METHOD_NAME_ADAPTER = TypeAdapter(RPCMethodName)


async def abort(
rabbitmq_rpc_client: RabbitMQRPCClient,
*,
rpc_namespace: RPCNamespace,
job_id: AsyncJobRpcId
) -> AsyncJobRpcAbort:
result = await rabbitmq_rpc_client.request(
rpc_namespace,
_RPC_METHOD_NAME_ADAPTER.validate_python("abort"),
job_id=job_id,
timeout_s=_DEFAULT_TIMEOUT_S,
)
assert isinstance(result, AsyncJobRpcAbort)
return result


async def get_status(
rabbitmq_rpc_client: RabbitMQRPCClient,
*,
rpc_namespace: RPCNamespace,
job_id: AsyncJobRpcId
) -> AsyncJobRpcStatus:
result = await rabbitmq_rpc_client.request(
rpc_namespace,
_RPC_METHOD_NAME_ADAPTER.validate_python("get_status"),
job_id=job_id,
timeout_s=_DEFAULT_TIMEOUT_S,
)
assert isinstance(result, AsyncJobRpcStatus)
return result


async def get_result(
rabbitmq_rpc_client: RabbitMQRPCClient,
*,
rpc_namespace: RPCNamespace,
job_id: AsyncJobRpcId
) -> AsyncJobRpcResult:
result = await rabbitmq_rpc_client.request(
rpc_namespace,
_RPC_METHOD_NAME_ADAPTER.validate_python("get_result"),
job_id=job_id,
timeout_s=_DEFAULT_TIMEOUT_S,
)
assert isinstance(result, AsyncJobRpcResult)
return result
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
from typing import Final

from models_library.api_schemas_rpc_data_export.tasks import (
TaskRpcGet,
TaskRpcId,
TaskRpcResult,
TaskRpcStatus,
)
from models_library.api_schemas_rpc_async_jobs.async_jobs import AsyncJobRpcGet
from models_library.api_schemas_storage import STORAGE_RPC_NAMESPACE
from models_library.api_schemas_storage.data_export_tasks import (
DataExportTaskAbortOutput,
from models_library.api_schemas_storage.data_export_async_jobs import (
DataExportTaskStartInput,
)
from models_library.rabbitmq_basic_types import RPCMethodName
Expand All @@ -23,51 +17,12 @@

async def start_data_export(
rabbitmq_rpc_client: RabbitMQRPCClient, *, paths: DataExportTaskStartInput
) -> TaskRpcGet:
) -> AsyncJobRpcGet:
result = await rabbitmq_rpc_client.request(
STORAGE_RPC_NAMESPACE,
_RPC_METHOD_NAME_ADAPTER.validate_python("start_data_export"),
paths=paths,
timeout_s=_DEFAULT_TIMEOUT_S,
)
assert isinstance(result, TaskRpcGet)
return result


async def abort_data_export(
rabbitmq_rpc_client: RabbitMQRPCClient, *, task_id: TaskRpcId
) -> DataExportTaskAbortOutput:
result = await rabbitmq_rpc_client.request(
STORAGE_RPC_NAMESPACE,
_RPC_METHOD_NAME_ADAPTER.validate_python("abort_data_export"),
task_id=task_id,
timeout_s=_DEFAULT_TIMEOUT_S,
)
assert isinstance(result, DataExportTaskAbortOutput)
return result


async def get_data_export_status(
rabbitmq_rpc_client: RabbitMQRPCClient, *, task_id: TaskRpcId
) -> TaskRpcStatus:
result = await rabbitmq_rpc_client.request(
STORAGE_RPC_NAMESPACE,
_RPC_METHOD_NAME_ADAPTER.validate_python("get_data_export_status"),
task_id=task_id,
timeout_s=_DEFAULT_TIMEOUT_S,
)
assert isinstance(result, TaskRpcStatus)
return result


async def get_data_export_result(
rabbitmq_rpc_client: RabbitMQRPCClient, *, task_id: TaskRpcId
) -> TaskRpcResult:
result = await rabbitmq_rpc_client.request(
STORAGE_RPC_NAMESPACE,
_RPC_METHOD_NAME_ADAPTER.validate_python("get_data_export_result"),
task_id=task_id,
timeout_s=_DEFAULT_TIMEOUT_S,
)
assert isinstance(result, TaskRpcResult)
assert isinstance(result, AsyncJobRpcGet)
return result
Loading
Loading