Skip to content

Commit 8ff8a1b

Browse files
Merge branch 'master' into is6880/hash-api-key-secret
2 parents fe5d750 + bb3b81c commit 8ff8a1b

File tree

249 files changed

+6040
-2943
lines changed

Some content is hidden

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

249 files changed

+6040
-2943
lines changed

api/specs/web-server/_long_running_tasks.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from servicelib.long_running_tasks._models import TaskGet, TaskStatus
1414
from simcore_service_webserver._meta import API_VTAG
1515
from simcore_service_webserver.tasks._exception_handlers import (
16-
_TO_HTTP_ERROR_MAP as data_export_http_error_map,
16+
_TO_HTTP_ERROR_MAP as export_data_http_error_map,
1717
)
1818

1919
router = APIRouter(
@@ -23,9 +23,9 @@
2323
],
2424
)
2525

26-
_data_export_responses: dict[int | str, dict[str, Any]] = {
26+
_export_data_responses: dict[int | str, dict[str, Any]] = {
2727
i.status_code: {"model": EnvelopedError}
28-
for i in data_export_http_error_map.values()
28+
for i in export_data_http_error_map.values()
2929
}
3030

3131

@@ -34,7 +34,7 @@
3434
response_model=Envelope[list[TaskGet]],
3535
name="list_tasks",
3636
description="Lists all long running tasks",
37-
responses=_data_export_responses,
37+
responses=_export_data_responses,
3838
)
3939
def get_async_jobs(): ...
4040

@@ -44,7 +44,7 @@ def get_async_jobs(): ...
4444
response_model=Envelope[TaskStatus],
4545
name="get_task_status",
4646
description="Retrieves the status of a task",
47-
responses=_data_export_responses,
47+
responses=_export_data_responses,
4848
)
4949
def get_async_job_status(
5050
_path_params: Annotated[_PathParam, Depends()],
@@ -55,7 +55,7 @@ def get_async_job_status(
5555
"/tasks/{task_id}",
5656
name="cancel_and_delete_task",
5757
description="Cancels and deletes a task",
58-
responses=_data_export_responses,
58+
responses=_export_data_responses,
5959
status_code=status.HTTP_204_NO_CONTENT,
6060
)
6161
def abort_async_job(
@@ -67,7 +67,7 @@ def abort_async_job(
6767
"/tasks/{task_id}/result",
6868
name="get_task_result",
6969
description="Retrieves the result of a task",
70-
responses=_data_export_responses,
70+
responses=_export_data_responses,
7171
)
7272
def get_async_job_result(
7373
_path_params: Annotated[_PathParam, Depends()],

api/specs/web-server/_projects_groups.py renamed to api/specs/web-server/_projects_access_rights.py

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,17 @@
77
from typing import Annotated
88

99
from fastapi import APIRouter, Depends, status
10+
from models_library.api_schemas_webserver.projects_access_rights import (
11+
ProjectsGroupsBodyParams,
12+
ProjectsGroupsPathParams,
13+
ProjectShare,
14+
ProjectShareAccepted,
15+
)
1016
from models_library.generics import Envelope
1117
from simcore_service_webserver._meta import API_VTAG
1218
from simcore_service_webserver.projects._controller._rest_schemas import (
1319
ProjectPathParams,
1420
)
15-
from simcore_service_webserver.projects._controller.groups_rest import (
16-
_ProjectsGroupsBodyParams,
17-
_ProjectsGroupsPathParams,
18-
)
1921
from simcore_service_webserver.projects._groups_service import ProjectGroupGet
2022

2123
router = APIRouter(
@@ -24,14 +26,30 @@
2426
)
2527

2628

29+
@router.post(
30+
"/projects/{project_id}:share",
31+
response_model=Envelope[ProjectShareAccepted],
32+
status_code=status.HTTP_202_ACCEPTED,
33+
responses={
34+
status.HTTP_202_ACCEPTED: {
35+
"description": "The request to share the project has been accepted, but the actual sharing process has to be confirmd."
36+
}
37+
},
38+
)
39+
async def share_project(
40+
_path: Annotated[ProjectPathParams, Depends()],
41+
_body: ProjectShare,
42+
): ...
43+
44+
2745
@router.post(
2846
"/projects/{project_id}/groups/{group_id}",
2947
response_model=Envelope[ProjectGroupGet],
3048
status_code=status.HTTP_201_CREATED,
3149
)
3250
async def create_project_group(
33-
_path: Annotated[_ProjectsGroupsPathParams, Depends()],
34-
_body: _ProjectsGroupsBodyParams,
51+
_path: Annotated[ProjectsGroupsPathParams, Depends()],
52+
_body: ProjectsGroupsBodyParams,
3553
): ...
3654

3755

@@ -47,8 +65,8 @@ async def list_project_groups(_path: Annotated[ProjectPathParams, Depends()]): .
4765
response_model=Envelope[ProjectGroupGet],
4866
)
4967
async def replace_project_group(
50-
_path: Annotated[_ProjectsGroupsPathParams, Depends()],
51-
_body: _ProjectsGroupsBodyParams,
68+
_path: Annotated[ProjectsGroupsPathParams, Depends()],
69+
_body: ProjectsGroupsBodyParams,
5270
): ...
5371

5472

@@ -57,5 +75,5 @@ async def replace_project_group(
5775
status_code=status.HTTP_204_NO_CONTENT,
5876
)
5977
async def delete_project_group(
60-
_path: Annotated[_ProjectsGroupsPathParams, Depends()],
78+
_path: Annotated[ProjectsGroupsPathParams, Depends()],
6179
): ...

api/specs/web-server/_storage.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
from simcore_service_webserver._meta import API_VTAG
3737
from simcore_service_webserver.storage.schemas import DatasetMetaData, FileMetaData
3838
from simcore_service_webserver.tasks._exception_handlers import (
39-
_TO_HTTP_ERROR_MAP as data_export_http_error_map,
39+
_TO_HTTP_ERROR_MAP as export_data_http_error_map,
4040
)
4141

4242
router = APIRouter(
@@ -221,9 +221,9 @@ async def is_completed_upload_file(
221221

222222

223223
# data export
224-
_data_export_responses: dict[int | str, dict[str, Any]] = {
224+
_export_data_responses: dict[int | str, dict[str, Any]] = {
225225
i.status_code: {"model": EnvelopedError}
226-
for i in data_export_http_error_map.values()
226+
for i in export_data_http_error_map.values()
227227
}
228228

229229

@@ -232,7 +232,7 @@ async def is_completed_upload_file(
232232
response_model=Envelope[TaskGet],
233233
name="export_data",
234234
description="Export data",
235-
responses=_data_export_responses,
235+
responses=_export_data_responses,
236236
)
237-
async def export_data(data_export: DataExportPost, location_id: LocationID):
237+
async def export_data(export_data: DataExportPost, location_id: LocationID):
238238
"""Trigger data export. Returns async job id for getting status and results"""

api/specs/web-server/openapi.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@
4242
"_nih_sparc",
4343
"_nih_sparc_redirections",
4444
"_projects",
45+
"_projects_access_rights",
4546
"_projects_comments",
4647
"_projects_folders",
47-
"_projects_groups",
4848
"_projects_metadata",
4949
"_projects_nodes",
5050
"_projects_nodes_pricing_unit", # after _projects_nodes

packages/aws-library/src/aws_library/ec2/_error_handler.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,8 @@ def ec2_exception_handler(
5959
[Callable[Concatenate[Self, P], Coroutine[Any, Any, R]]],
6060
Callable[Concatenate[Self, P], Coroutine[Any, Any, R]],
6161
]:
62-
"""
63-
Raises:
64-
SSMAccessError:
65-
"""
66-
6762
def decorator(
68-
func: Callable[Concatenate[Self, P], Coroutine[Any, Any, R]]
63+
func: Callable[Concatenate[Self, P], Coroutine[Any, Any, R]],
6964
) -> Callable[Concatenate[Self, P], Coroutine[Any, Any, R]]:
7065
@functools.wraps(func)
7166
async def wrapper(self: Self, *args: P.args, **kwargs: P.kwargs) -> R:

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
MULTIPART_COPY_THRESHOLD: Final[ByteSize] = TypeAdapter(ByteSize).validate_python(
1010
"100MiB"
1111
)
12+
STREAM_READER_CHUNK_SIZE: Final[ByteSize] = TypeAdapter(ByteSize).validate_python(
13+
"10MiB"
14+
)
1215

1316
PRESIGNED_LINK_MAX_SIZE: Final[ByteSize] = TypeAdapter(ByteSize).validate_python("5GiB")
1417
S3_MAX_FILE_SIZE: Final[ByteSize] = TypeAdapter(ByteSize).validate_python("5TiB")

packages/aws-library/tests/test_s3_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
from aiohttp import ClientSession
2828
from aws_library.s3._client import _AWS_MAX_ITEMS_PER_PAGE, S3ObjectKey, SimcoreS3API
2929
from aws_library.s3._constants import (
30-
MULTIPART_COPY_THRESHOLD,
3130
MULTIPART_UPLOADS_MIN_TOTAL_SIZE,
31+
STREAM_READER_CHUNK_SIZE,
3232
)
3333
from aws_library.s3._errors import (
3434
S3BucketInvalidError,
@@ -1902,7 +1902,7 @@ async def test_workflow_compress_s3_objects_and_local_files_in_a_single_archive_
19021902
get_zip_bytes_iter(
19031903
archive_entries,
19041904
progress_bar=progress_bar,
1905-
chunk_size=MULTIPART_COPY_THRESHOLD,
1905+
chunk_size=STREAM_READER_CHUNK_SIZE,
19061906
)
19071907
),
19081908
)

packages/common-library/src/common_library/json_serialization.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
""" Helpers for json serialization
2-
- built-in json-like API
3-
- implemented using orjson, which performs better. SEE https://github.com/ijl/orjson?tab=readme-ov-file#performance
1+
"""Helpers for json serialization
2+
- built-in json-like API
3+
- implemented using orjson, which performs better. SEE https://github.com/ijl/orjson?tab=readme-ov-file#performance
44
"""
55

66
import datetime
@@ -118,6 +118,28 @@ def pydantic_encoder(obj: Any) -> Any:
118118
raise TypeError(msg)
119119

120120

121+
def representation_encoder(obj: Any):
122+
"""
123+
A fallback encoder that uses `pydantic_encoder` to serialize objects.
124+
If serialization fails, it falls back to using `str(obj)`.
125+
126+
This is practical for representation purposes, such as logging or debugging.
127+
128+
Example:
129+
>>> from common_library.json_serialization import json_dumps, representation_encoder
130+
>>> class CustomObject:
131+
... def __str__(self):
132+
... return "CustomObjectRepresentation"
133+
>>> obj = CustomObject()
134+
>>> json_dumps(obj, default=representation_encoder)
135+
'"CustomObjectRepresentation"'
136+
"""
137+
try:
138+
return pydantic_encoder(obj)
139+
except TypeError:
140+
return str(obj)
141+
142+
121143
def json_dumps(
122144
obj: Any,
123145
*,

packages/common-library/tests/test_json_serialization.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
SeparatorTuple,
1414
json_dumps,
1515
json_loads,
16+
representation_encoder,
1617
)
1718
from faker import Faker
1819
from pydantic import AnyHttpUrl, AnyUrl, BaseModel, Field, HttpUrl, TypeAdapter
@@ -110,3 +111,25 @@ class M(BaseModel):
110111
http_url=faker.url(),
111112
)
112113
json_dumps(obj)
114+
115+
116+
def test_json_dumps_with_representation_encoder():
117+
class CustomObject:
118+
def __str__(self):
119+
return "CustomObjectRepresentation"
120+
121+
class SomeModel(BaseModel):
122+
x: int
123+
124+
obj = {
125+
"custom": CustomObject(),
126+
"some": SomeModel(x=42),
127+
}
128+
129+
# Using representation_encoder as the default encoder
130+
result = json_dumps(obj, default=representation_encoder, indent=1)
131+
132+
assert (
133+
result
134+
== '{\n "custom": "CustomObjectRepresentation",\n "some": {\n "x": 42\n }\n}'
135+
)

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ class AccessRights(BaseModel):
1010

1111
model_config = ConfigDict(extra="forbid")
1212

13+
def verify_access_integrity(self):
14+
"""Helper function that checks extra constraints in access-rights flags"""
15+
if self.write and not self.read:
16+
msg = "Write access requires read access"
17+
raise ValueError(msg)
18+
if self.delete and not self.write:
19+
msg = "Delete access requires read access"
20+
raise ValueError(msg)
21+
return self
22+
1323

1424
class ExecutableAccessRights(BaseModel):
1525
write: Annotated[bool, Field(description="can change executable settings")]

0 commit comments

Comments
 (0)