Skip to content

Commit f7c8b53

Browse files
Propagate data export endpoints to webserver (#7244)
1 parent 7be787f commit f7c8b53

File tree

81 files changed

+1357
-317
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+1357
-317
lines changed

api/specs/web-server/Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,6 @@ install-dev install: _check_venv_active
1616
.PHONY: all
1717
all: _check_venv_active install
1818
python openapi.py
19+
20+
.PHONY: openapi-specs
21+
openapi-specs: all

api/specs/web-server/_storage.py

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@
55

66

77
from typing import TypeAlias
8+
from uuid import UUID
89

910
from fastapi import APIRouter, Query, status
10-
from models_library.generics import Envelope
11-
from models_library.projects_nodes_io import LocationID
12-
from models_library.storage_schemas import (
11+
from models_library.api_schemas_storage.storage_schemas import (
1312
FileLocation,
1413
FileMetaDataGet,
1514
FileUploadCompleteFutureResponse,
@@ -19,6 +18,15 @@
1918
LinkType,
2019
PresignedLink,
2120
)
21+
from models_library.api_schemas_webserver.storage import (
22+
DataExportPost,
23+
StorageAsyncJobGet,
24+
StorageAsyncJobResult,
25+
StorageAsyncJobStatus,
26+
)
27+
from models_library.generics import Envelope
28+
from models_library.projects_nodes_io import LocationID
29+
from models_library.users import UserID
2230
from pydantic import AnyUrl, ByteSize
2331
from simcore_service_webserver._meta import API_VTAG
2432
from simcore_service_webserver.storage.schemas import DatasetMetaData, FileMetaData
@@ -167,3 +175,49 @@ async def is_completed_upload_file(
167175
location_id: LocationID, file_id: StorageFileIDStr, future_id: str
168176
):
169177
"""Returns state of upload completion"""
178+
179+
180+
# data export
181+
@router.post(
182+
"/storage/locations/{location_id}/export-data",
183+
response_model=Envelope[StorageAsyncJobGet],
184+
name="export_data",
185+
description="Export data",
186+
)
187+
async def export_data(data_export: DataExportPost, location_id: LocationID):
188+
"""Trigger data export. Returns async job id for getting status and results"""
189+
190+
191+
@router.get(
192+
"/storage/async-jobs/{job_id}/status",
193+
response_model=Envelope[StorageAsyncJobStatus],
194+
name="get_async_job_status",
195+
)
196+
async def get_async_job_status(storage_async_job_get: StorageAsyncJobGet, job_id: UUID):
197+
"""Get async job status"""
198+
199+
200+
@router.post(
201+
"/storage/async-jobs/{job_id}:abort",
202+
name="abort_async_job",
203+
)
204+
async def abort_async_job(storage_async_job_get: StorageAsyncJobGet, job_id: UUID):
205+
"""aborts execution of an async job"""
206+
207+
208+
@router.get(
209+
"/storage/async-jobs/{job_id}/result",
210+
response_model=Envelope[StorageAsyncJobResult],
211+
name="get_async_job_result",
212+
)
213+
async def get_async_job_result(storage_async_job_get: StorageAsyncJobGet, job_id: UUID):
214+
"""Get the result of the async job"""
215+
216+
217+
@router.get(
218+
"/storage/async-jobs",
219+
response_model=Envelope[list[StorageAsyncJobGet]],
220+
name="get_async_jobs",
221+
)
222+
async def get_async_jobs(user_id: UserID):
223+
"""Retrunsa list of async jobs for the user"""

packages/aws-library/src/aws_library/s3/_client.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,13 @@
1313
from boto3.s3.transfer import TransferConfig
1414
from botocore import exceptions as botocore_exc
1515
from botocore.client import Config
16+
from models_library.api_schemas_storage.storage_schemas import (
17+
ETag,
18+
S3BucketName,
19+
UploadedPart,
20+
)
1621
from models_library.basic_types import SHA256Str
1722
from models_library.bytes_iters import BytesIter, DataSize
18-
from models_library.storage_schemas import ETag, S3BucketName, UploadedPart
1923
from pydantic import AnyUrl, ByteSize, TypeAdapter
2024
from servicelib.bytes_iters import DEFAULT_READ_CHUNK_SIZE, BytesStreamer
2125
from servicelib.logging_utils import log_catch, log_context

packages/aws-library/src/aws_library/s3/_models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
from dataclasses import dataclass
33
from typing import TypeAlias
44

5+
from models_library.api_schemas_storage.storage_schemas import ETag
56
from models_library.basic_types import SHA256Str
6-
from models_library.storage_schemas import ETag
77
from pydantic import AnyUrl, BaseModel, ByteSize
88
from types_aiobotocore_s3.type_defs import HeadObjectOutputTypeDef, ObjectTypeDef
99

packages/aws-library/tests/test_s3_client.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@
3838
)
3939
from aws_library.s3._models import MultiPartUploadLinks
4040
from faker import Faker
41+
from models_library.api_schemas_storage.storage_schemas import (
42+
S3BucketName,
43+
UploadedPart,
44+
)
4145
from models_library.basic_types import SHA256Str
42-
from models_library.storage_schemas import S3BucketName, UploadedPart
4346
from moto.server import ThreadedMotoServer
4447
from pydantic import AnyUrl, ByteSize, TypeAdapter
4548
from pytest_benchmark.plugin import BenchmarkFixture
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from datetime import datetime
2+
from typing import Any, TypeAlias
3+
from uuid import UUID
4+
5+
from models_library.users import UserID
6+
from pydantic import BaseModel, model_validator
7+
from typing_extensions import Self
8+
9+
from ..progress_bar import ProgressReport
10+
11+
AsyncJobId: TypeAlias = UUID
12+
13+
14+
class AsyncJobStatus(BaseModel):
15+
job_id: AsyncJobId
16+
progress: ProgressReport
17+
done: bool
18+
started: datetime
19+
stopped: datetime | None
20+
21+
@model_validator(mode="after")
22+
def _check_consistency(self) -> Self:
23+
is_done = self.done
24+
is_stopped = self.stopped is not None
25+
26+
if is_done != is_stopped:
27+
msg = f"Inconsistent data: {self.done=}, {self.stopped=}"
28+
raise ValueError(msg)
29+
return self
30+
31+
32+
class AsyncJobResult(BaseModel):
33+
result: Any | None
34+
error: Any | None
35+
36+
37+
class AsyncJobGet(BaseModel):
38+
job_id: AsyncJobId
39+
job_name: str
40+
41+
42+
class AsyncJobAbort(BaseModel):
43+
result: bool
44+
job_id: AsyncJobId
45+
46+
47+
class AsyncJobAccessData(BaseModel):
48+
"""Data for controlling access to an async job"""
49+
50+
user_id: UserID | None
51+
product_name: str
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from common_library.errors_classes import OsparcErrorMixin
2+
3+
4+
class BaseAsyncjobRpcError(OsparcErrorMixin, RuntimeError):
5+
pass
6+
7+
8+
class StatusError(BaseAsyncjobRpcError):
9+
msg_template: str = "Could not get status of job {job_id}"
10+
11+
12+
class ResultError(BaseAsyncjobRpcError):
13+
msg_template: str = "Could not get results of job {job_id}"

packages/models-library/src/models_library/api_schemas_rpc_data_export/tasks.py

Lines changed: 0 additions & 36 deletions
This file was deleted.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# pylint: disable=R6301
2+
from pathlib import Path
3+
4+
from common_library.errors_classes import OsparcErrorMixin
5+
from models_library.projects_nodes_io import LocationID
6+
from models_library.users import UserID
7+
from pydantic import BaseModel, Field
8+
9+
10+
class DataExportTaskStartInput(BaseModel):
11+
user_id: UserID
12+
product_name: str
13+
location_id: LocationID
14+
paths: list[Path] = Field(..., min_length=1)
15+
16+
17+
### Exceptions
18+
19+
20+
class StorageRpcError(OsparcErrorMixin, RuntimeError):
21+
pass
22+
23+
24+
class InvalidFileIdentifierError(StorageRpcError):
25+
msg_template: str = "Could not find the file {file_id}"
26+
27+
28+
class AccessRightError(StorageRpcError):
29+
msg_template: str = "User {user_id} does not have access to file {file_id}"
30+
31+
32+
class DataExportError(StorageRpcError):
33+
msg_template: str = "Could not complete data export job with id {job_id}"

0 commit comments

Comments
 (0)