Skip to content

Commit 94e5d0a

Browse files
Merge branch 'master' into is7581/wait-for-asyncio-tasks-when-aborting
2 parents ba50b78 + 1b6b386 commit 94e5d0a

File tree

32 files changed

+454
-189
lines changed

32 files changed

+454
-189
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@
2222
)
2323

2424
__all__: tuple[str, ...] = (
25-
"CopiedBytesTransferredCallback",
26-
"MultiPartUploadLinks",
2725
"PRESIGNED_LINK_MAX_SIZE",
2826
"S3_MAX_FILE_SIZE",
27+
"CopiedBytesTransferredCallback",
28+
"MultiPartUploadLinks",
2929
"S3AccessError",
3030
"S3BucketInvalidError",
3131
"S3DestinationNotEmptyError",
@@ -37,8 +37,8 @@
3737
"S3RuntimeError",
3838
"S3UploadNotFoundError",
3939
"SimcoreS3API",
40-
"UploadedBytesTransferredCallback",
4140
"UploadID",
41+
"UploadedBytesTransferredCallback",
4242
)
4343

4444
# nopycln: file

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import functools
44
import logging
55
import urllib.parse
6+
import warnings
67
from collections.abc import AsyncGenerator, Sequence
78
from dataclasses import dataclass, field
89
from pathlib import Path
@@ -11,6 +12,7 @@
1112
import aioboto3
1213
from aiobotocore.session import ClientCreatorContext
1314
from boto3.s3.transfer import TransferConfig
15+
from botocore import __version__ as botocore_version
1416
from botocore import exceptions as botocore_exc
1517
from botocore.client import Config
1618
from models_library.api_schemas_storage.storage_schemas import (
@@ -20,6 +22,7 @@
2022
)
2123
from models_library.basic_types import SHA256Str
2224
from models_library.bytes_iters import BytesIter, DataSize
25+
from packaging import version
2326
from pydantic import AnyUrl, ByteSize, TypeAdapter
2427
from servicelib.bytes_iters import DEFAULT_READ_CHUNK_SIZE, BytesStreamer
2528
from servicelib.logging_utils import log_catch, log_context
@@ -51,6 +54,22 @@
5154
)
5255
from ._utils import compute_num_file_chunks, create_final_prefix
5356

57+
_BOTOCORE_VERSION: Final[version.Version] = version.parse(botocore_version)
58+
_MAX_BOTOCORE_VERSION_COMPATIBLE_WITH_CEPH_S3: Final[version.Version] = version.parse(
59+
"1.36.0"
60+
)
61+
62+
63+
def _check_botocore_version() -> None:
64+
if _BOTOCORE_VERSION >= _MAX_BOTOCORE_VERSION_COMPATIBLE_WITH_CEPH_S3:
65+
warnings.warn(
66+
f"Botocore version {botocore_version} is not supported for file uploads with CEPH S3 until CEPH is updated. "
67+
"Please use a version < 1.36.0. The upload operation will likely fail.",
68+
RuntimeWarning,
69+
stacklevel=2,
70+
)
71+
72+
5473
_logger = logging.getLogger(__name__)
5574

5675
_S3_MAX_CONCURRENCY_DEFAULT: Final[int] = 10
@@ -504,6 +523,9 @@ async def upload_file(
504523
bytes_transfered_cb: UploadedBytesTransferredCallback | None,
505524
) -> None:
506525
"""upload a file using aioboto3 transfer manager (e.g. works >5Gb and creates multiple threads)"""
526+
527+
_check_botocore_version()
528+
507529
upload_options: dict[str, Any] = {
508530
"Bucket": bucket,
509531
"Key": object_key,
@@ -528,6 +550,9 @@ async def copy_object(
528550
object_metadata: S3MetaData | None = None,
529551
) -> None:
530552
"""copy a file in S3 using aioboto3 transfer manager (e.g. works >5Gb and creates multiple threads)"""
553+
554+
_check_botocore_version()
555+
531556
copy_options: dict[str, Any] = {
532557
"CopySource": {"Bucket": bucket, "Key": src_object_key},
533558
"Bucket": bucket,
@@ -634,6 +659,7 @@ async def upload_object_from_file_like(
634659
file_like_reader: FileLikeReader,
635660
) -> None:
636661
"""streams write an object in S3 from an AsyncIterable[bytes]"""
662+
_check_botocore_version()
637663
await self._client.upload_fileobj(file_like_reader, bucket_name, object_key) # type: ignore[arg-type]
638664

639665
@staticmethod

packages/models-library/src/models_library/projects.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ class ProjectAtDB(BaseProjectModel):
113113

114114
published: Annotated[
115115
bool | None,
116-
Field(default=False, description="Defines if a study is available publicly"),
117-
]
116+
Field(description="Defines if a study is available publicly"),
117+
] = False
118118

119119
@field_validator("project_type", mode="before")
120120
@classmethod

packages/models-library/src/models_library/projects_nodes.py

Lines changed: 159 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
Models Node as a central element in a project's pipeline
2+
Models Node as a central element in a project's pipeline
33
"""
44

55
from typing import Annotated, Any, TypeAlias, Union
@@ -17,6 +17,7 @@
1717
StringConstraints,
1818
field_validator,
1919
)
20+
from pydantic.config import JsonDict
2021

2122
from .basic_types import EnvVarKey, KeyIDStr
2223
from .projects_access import AccessEnum
@@ -71,26 +72,41 @@
7172

7273

7374
class NodeState(BaseModel):
74-
modified: bool = Field(
75-
default=True, description="true if the node's outputs need to be re-computed"
76-
)
77-
dependencies: set[NodeID] = Field(
78-
default_factory=set,
79-
description="contains the node inputs dependencies if they need to be computed first",
80-
)
81-
current_status: RunningState = Field(
82-
default=RunningState.NOT_STARTED,
83-
description="the node's current state",
84-
alias="currentStatus",
85-
)
86-
progress: float | None = Field(
87-
default=0,
88-
ge=0.0,
89-
le=1.0,
90-
description="current progress of the task if available (None if not started or not a computational task)",
91-
)
75+
modified: Annotated[
76+
bool,
77+
Field(
78+
description="true if the node's outputs need to be re-computed",
79+
),
80+
] = True
81+
82+
dependencies: Annotated[
83+
set[NodeID],
84+
Field(
85+
default_factory=set,
86+
description="contains the node inputs dependencies if they need to be computed first",
87+
),
88+
] = DEFAULT_FACTORY
89+
90+
current_status: Annotated[
91+
RunningState,
92+
Field(
93+
description="the node's current state",
94+
alias="currentStatus",
95+
),
96+
] = RunningState.NOT_STARTED
97+
98+
progress: Annotated[
99+
float | None,
100+
Field(
101+
ge=0.0,
102+
le=1.0,
103+
description="current progress of the task if available (None if not started or not a computational task)",
104+
),
105+
] = 0
106+
92107
model_config = ConfigDict(
93108
extra="forbid",
109+
populate_by_name=True,
94110
json_schema_extra={
95111
"examples": [
96112
{
@@ -113,24 +129,35 @@ class NodeState(BaseModel):
113129
)
114130

115131

132+
def _convert_old_enum_name(v) -> RunningState:
133+
if v == "FAILURE":
134+
return RunningState.FAILED
135+
return RunningState(v)
136+
137+
116138
class Node(BaseModel):
117-
key: ServiceKey = Field(
118-
...,
119-
description="distinctive name for the node based on the docker registry path",
120-
examples=[
121-
"simcore/services/comp/itis/sleeper",
122-
"simcore/services/dynamic/3dviewer",
123-
"simcore/services/frontend/file-picker",
124-
],
125-
)
126-
version: ServiceVersion = Field(
127-
...,
128-
description="semantic version number of the node",
129-
examples=["1.0.0", "0.0.1"],
130-
)
131-
label: str = Field(
132-
..., description="The short name of the node", examples=["JupyterLab"]
133-
)
139+
key: Annotated[
140+
ServiceKey,
141+
Field(
142+
description="distinctive name for the node based on the docker registry path",
143+
examples=[
144+
"simcore/services/comp/itis/sleeper",
145+
"simcore/services/dynamic/3dviewer",
146+
"simcore/services/frontend/file-picker",
147+
],
148+
),
149+
]
150+
version: Annotated[
151+
ServiceVersion,
152+
Field(
153+
description="semantic version number of the node",
154+
examples=["1.0.0", "0.0.1"],
155+
),
156+
]
157+
label: Annotated[
158+
str,
159+
Field(description="The short name of the node", examples=["JupyterLab"]),
160+
]
134161
progress: Annotated[
135162
float | None,
136163
Field(
@@ -204,9 +231,9 @@ class Node(BaseModel):
204231
Field(default_factory=dict, description="values of output properties"),
205232
] = DEFAULT_FACTORY
206233

207-
output_node: Annotated[
208-
bool | None, Field(deprecated=True, alias="outputNode")
209-
] = None
234+
output_node: Annotated[bool | None, Field(deprecated=True, alias="outputNode")] = (
235+
None
236+
)
210237

211238
output_nodes: Annotated[
212239
list[NodeID] | None,
@@ -255,24 +282,109 @@ def _convert_empty_str_to_none(cls, v):
255282
return None
256283
return v
257284

258-
@classmethod
259-
def _convert_old_enum_name(cls, v) -> RunningState:
260-
if v == "FAILURE":
261-
return RunningState.FAILED
262-
return RunningState(v)
263-
264285
@field_validator("state", mode="before")
265286
@classmethod
266287
def _convert_from_enum(cls, v):
267288
if isinstance(v, str):
289+
268290
# the old version of state was a enum of RunningState
269-
running_state_value = cls._convert_old_enum_name(v)
270-
return NodeState(currentStatus=running_state_value)
291+
running_state_value = _convert_old_enum_name(v)
292+
return NodeState(current_status=running_state_value)
271293
return v
272294

295+
@staticmethod
296+
def _update_json_schema_extra(schema: JsonDict) -> None:
297+
schema.update(
298+
{
299+
"examples": [
300+
# Minimal example with only required fields
301+
{
302+
"key": "simcore/services/comp/no_ports",
303+
"version": "1.0.0",
304+
"label": "Sleep",
305+
},
306+
# Complete example with optional fields
307+
{
308+
"key": "simcore/services/comp/only_inputs",
309+
"version": "1.0.0",
310+
"label": "Only INputs",
311+
"inputs": {
312+
"input_1": 1,
313+
"input_2": 2,
314+
"input_3": 3,
315+
},
316+
},
317+
# Complete example with optional fields
318+
{
319+
"key": "simcore/services/comp/only_outputs",
320+
"version": "1.0.0",
321+
"label": "Only Outputs",
322+
"outputs": {
323+
"output_1": 1,
324+
"output_2": 2,
325+
"output_3": 3,
326+
},
327+
},
328+
# Example with all possible input and output types
329+
{
330+
"key": "simcore/services/comp/itis/all-types",
331+
"version": "1.0.0",
332+
"label": "All Types Demo",
333+
"inputs": {
334+
"boolean_input": True,
335+
"integer_input": 42,
336+
"float_input": 3.14159,
337+
"string_input": "text value",
338+
"json_input": {"key": "value", "nested": {"data": 123}},
339+
"port_link_input": {
340+
"nodeUuid": "f2700a54-adcf-45d4-9881-01ec30fd75a2",
341+
"output": "out_1",
342+
},
343+
"simcore_file_link": {
344+
"store": "simcore.s3",
345+
"path": "123e4567-e89b-12d3-a456-426614174000/test.csv",
346+
},
347+
"datcore_file_link": {
348+
"store": "datcore",
349+
"dataset": "N:dataset:123",
350+
"path": "path/to/file.txt",
351+
},
352+
"download_link": {
353+
"downloadLink": "https://example.com/downloadable/file.txt"
354+
},
355+
"array_input": [1, 2, 3, 4, 5],
356+
"object_input": {"name": "test", "value": 42},
357+
},
358+
"outputs": {
359+
"boolean_output": False,
360+
"integer_output": 100,
361+
"float_output": 2.71828,
362+
"string_output": "result text",
363+
"json_output": {"status": "success", "data": [1, 2, 3]},
364+
"simcore_file_output": {
365+
"store": "simcore.s3",
366+
"path": "987e6543-e21b-12d3-a456-426614174000/result.csv",
367+
},
368+
"datcore_file_output": {
369+
"store": "datcore",
370+
"dataset": "N:dataset:456",
371+
"path": "results/output.txt",
372+
},
373+
"download_link_output": {
374+
"downloadLink": "https://example.com/results/download.txt"
375+
},
376+
"array_output": ["a", "b", "c", "d"],
377+
"object_output": {"status": "complete", "count": 42},
378+
},
379+
},
380+
],
381+
}
382+
)
383+
273384
model_config = ConfigDict(
274385
extra="forbid",
275386
populate_by_name=True,
387+
json_schema_extra=_update_json_schema_extra,
276388
)
277389

278390

0 commit comments

Comments
 (0)