Skip to content

Commit 2017816

Browse files
authored
Merge branch 'master' into feature/trash-bin
2 parents 245a10b + 0718e14 commit 2017816

File tree

85 files changed

+2207
-1349
lines changed

Some content is hidden

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

85 files changed

+2207
-1349
lines changed

api/specs/web-server/_folders.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,28 @@ async def list_folders(
6363
...
6464

6565

66+
@router.get(
67+
"/folders:search",
68+
response_model=Envelope[list[FolderGet]],
69+
)
70+
async def list_folders_full_search(
71+
params: Annotated[PageQueryParameters, Depends()],
72+
text: str | None = None,
73+
order_by: Annotated[
74+
Json,
75+
Query(
76+
description="Order by field (modified_at|name|description) and direction (asc|desc). The default sorting order is ascending.",
77+
example='{"field": "name", "direction": "desc"}',
78+
),
79+
] = '{"field": "modified_at", "direction": "desc"}',
80+
filters: Annotated[
81+
Json | None,
82+
Query(description=FolderFilters.schema_json(indent=1)),
83+
] = None,
84+
):
85+
...
86+
87+
6688
@router.get(
6789
"/folders/{folder_id}",
6890
response_model=Envelope[FolderGet],

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

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,42 @@
11
from datetime import datetime
2+
from enum import auto
23
from typing import TypeAlias
34

4-
from models_library.users import GroupID, UserID
5-
from models_library.workspaces import WorkspaceID
6-
from pydantic import BaseModel, Field, PositiveInt
5+
from pydantic import BaseModel, Field, PositiveInt, validator
6+
7+
from .access_rights import AccessRights
8+
from .users import GroupID, UserID
9+
from .utils.enums import StrAutoEnum
10+
from .workspaces import WorkspaceID
711

812
FolderID: TypeAlias = PositiveInt
913

1014

15+
class FolderScope(StrAutoEnum):
16+
ROOT = auto()
17+
SPECIFIC = auto()
18+
ALL = auto()
19+
20+
21+
class FolderQuery(BaseModel):
22+
folder_scope: FolderScope
23+
folder_id: PositiveInt | None = None
24+
25+
@validator("folder_id", pre=True, always=True)
26+
@classmethod
27+
def validate_folder_id(cls, value, values):
28+
scope = values.get("folder_scope")
29+
if scope == FolderScope.SPECIFIC and value is None:
30+
raise ValueError(
31+
"folder_id must be provided when folder_scope is SPECIFIC."
32+
)
33+
if scope != FolderScope.SPECIFIC and value is not None:
34+
raise ValueError(
35+
"folder_id should be None when folder_scope is not SPECIFIC."
36+
)
37+
return value
38+
39+
1140
#
1241
# DB
1342
#
@@ -38,3 +67,10 @@ class FolderDB(BaseModel):
3867

3968
class Config:
4069
orm_mode = True
70+
71+
72+
class UserFolderAccessRightsDB(FolderDB):
73+
my_access_rights: AccessRights
74+
75+
class Config:
76+
orm_mode = True

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

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,41 @@
11
from datetime import datetime
2+
from enum import auto
23
from typing import TypeAlias
34

4-
from models_library.access_rights import AccessRights
5-
from models_library.users import GroupID
6-
from pydantic import BaseModel, Field, PositiveInt
5+
from pydantic import BaseModel, Field, PositiveInt, validator
6+
7+
from .access_rights import AccessRights
8+
from .users import GroupID
9+
from .utils.enums import StrAutoEnum
710

811
WorkspaceID: TypeAlias = PositiveInt
912

1013

14+
class WorkspaceScope(StrAutoEnum):
15+
PRIVATE = auto()
16+
SHARED = auto()
17+
ALL = auto()
18+
19+
20+
class WorkspaceQuery(BaseModel):
21+
workspace_scope: WorkspaceScope
22+
workspace_id: PositiveInt | None = None
23+
24+
@validator("workspace_id", pre=True, always=True)
25+
@classmethod
26+
def validate_workspace_id(cls, value, values):
27+
scope = values.get("workspace_scope")
28+
if scope == WorkspaceScope.SHARED and value is None:
29+
raise ValueError(
30+
"workspace_id must be provided when workspace_scope is SHARED."
31+
)
32+
if scope != WorkspaceScope.SHARED and value is not None:
33+
raise ValueError(
34+
"workspace_id should be None when workspace_scope is not SHARED."
35+
)
36+
return value
37+
38+
1139
#
1240
# DB
1341
#
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""add cancellation mark
2+
3+
Revision ID: 8bfe65a5e294
4+
Revises: 5ad02358751a
5+
Create Date: 2024-11-08 14:40:59.266181+00:00
6+
7+
"""
8+
import sqlalchemy as sa
9+
from alembic import op
10+
11+
# revision identifiers, used by Alembic.
12+
revision = "8bfe65a5e294"
13+
down_revision = "5ad02358751a"
14+
branch_labels = None
15+
depends_on = None
16+
17+
18+
def upgrade():
19+
# ### commands auto generated by Alembic - please adjust! ###
20+
op.add_column(
21+
"comp_runs", sa.Column("cancelled", sa.DateTime(timezone=True), nullable=True)
22+
)
23+
# ### end Alembic commands ###
24+
25+
26+
def downgrade():
27+
# ### commands auto generated by Alembic - please adjust! ###
28+
op.drop_column("comp_runs", "cancelled")
29+
# ### end Alembic commands ###

packages/postgres-database/src/simcore_postgres_database/models/comp_runs.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@
9999
nullable=True,
100100
doc="When the run was finished",
101101
),
102+
sa.Column(
103+
"cancelled",
104+
sa.DateTime(timezone=True),
105+
nullable=True,
106+
doc="If filled, when cancellation was requested",
107+
),
102108
sa.Column("metadata", JSONB, nullable=True, doc="the run optional metadata"),
103109
sa.Column(
104110
"use_on_demand_clusters",
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
def assemble_array_groups(user_group_ids: list[int]) -> str:
2+
return (
3+
"array[]::text[]"
4+
if len(user_group_ids) == 0
5+
else f"""array[{', '.join(f"'{group_id}'" for group_id in user_group_ids)}]"""
6+
)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from simcore_postgres_database.models.groups import user_to_groups
2+
from simcore_postgres_database.models.workspaces_access_rights import (
3+
workspaces_access_rights,
4+
)
5+
from sqlalchemy import func
6+
from sqlalchemy.dialects.postgresql import BOOLEAN, INTEGER
7+
from sqlalchemy.sql import Subquery, select
8+
9+
10+
def create_my_workspace_access_rights_subquery(user_id: int) -> Subquery:
11+
return (
12+
select(
13+
workspaces_access_rights.c.workspace_id,
14+
func.json_build_object(
15+
"read",
16+
func.max(workspaces_access_rights.c.read.cast(INTEGER)).cast(BOOLEAN),
17+
"write",
18+
func.max(workspaces_access_rights.c.write.cast(INTEGER)).cast(BOOLEAN),
19+
"delete",
20+
func.max(workspaces_access_rights.c.delete.cast(INTEGER)).cast(BOOLEAN),
21+
).label("my_access_rights"),
22+
)
23+
.select_from(
24+
workspaces_access_rights.join(
25+
user_to_groups, user_to_groups.c.gid == workspaces_access_rights.c.gid
26+
)
27+
)
28+
.where(user_to_groups.c.uid == user_id)
29+
.group_by(workspaces_access_rights.c.workspace_id)
30+
).subquery("my_workspace_access_rights_subquery")

packages/service-library/src/servicelib/redis.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ async def _cancel_or_warn(task: Task) -> None:
6060
@dataclass
6161
class RedisClientSDK:
6262
redis_dsn: str
63+
client_name: str
6364
decode_responses: bool = _DEFAULT_DECODE_RESPONSES
6465
health_check_interval: datetime.timedelta = _DEFAULT_HEALTH_CHECK_INTERVAL
6566

@@ -86,7 +87,7 @@ def __post_init__(self):
8687
socket_connect_timeout=_DEFAULT_SOCKET_TIMEOUT.total_seconds(),
8788
encoding="utf-8",
8889
decode_responses=self.decode_responses,
89-
auto_close_connection_pool=True,
90+
client_name=self.client_name,
9091
)
9192

9293
@retry(**RedisRetryPolicyUponInitialization(_logger).kwargs)
@@ -238,6 +239,7 @@ class RedisClientsManager:
238239

239240
databases_configs: set[RedisManagerDBConfig]
240241
settings: RedisSettings
242+
client_name: str
241243

242244
_client_sdks: dict[RedisDatabase, RedisClientSDK] = field(default_factory=dict)
243245

@@ -247,6 +249,7 @@ async def setup(self) -> None:
247249
redis_dsn=self.settings.build_redis_dsn(config.database),
248250
decode_responses=config.decode_responses,
249251
health_check_interval=config.health_check_interval,
252+
client_name=f"{self.client_name}",
250253
)
251254

252255
for client in self._client_sdks.values():

packages/service-library/tests/conftest.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,12 @@ async def _(
8080
database: RedisDatabase, decode_response: bool = True # noqa: FBT002
8181
) -> AsyncIterator[RedisClientSDK]:
8282
redis_resources_dns = redis_service.build_redis_dsn(database)
83-
client = RedisClientSDK(redis_resources_dns, decode_responses=decode_response)
83+
client = RedisClientSDK(
84+
redis_resources_dns, decode_responses=decode_response, client_name="pytest"
85+
)
8486
assert client
8587
assert client.redis_dsn == redis_resources_dns
88+
assert client.client_name == "pytest"
8689
await client.setup()
8790

8891
yield client
@@ -94,7 +97,9 @@ async def _cleanup_redis_data(clients_manager: RedisClientsManager) -> None:
9497
await clients_manager.client(db).redis.flushall()
9598

9699
async with RedisClientsManager(
97-
{RedisManagerDBConfig(db) for db in RedisDatabase}, redis_service
100+
{RedisManagerDBConfig(db) for db in RedisDatabase},
101+
redis_service,
102+
client_name="pytest",
98103
) as clients_manager:
99104
await _cleanup_redis_data(clients_manager)
100105
yield _

packages/service-library/tests/deferred_tasks/example_app.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ def __init__(self, redis_settings: RedisSettings, port: int) -> None:
6060
self.redis: Redis = RedisClientSDK(
6161
redis_settings.build_redis_dsn(RedisDatabase.DEFERRED_TASKS),
6262
decode_responses=True,
63+
client_name="example_app",
6364
).redis
6465
self.port = port
6566

@@ -84,6 +85,7 @@ def __init__(
8485
self._redis_client = RedisClientSDK(
8586
redis_settings.build_redis_dsn(RedisDatabase.DEFERRED_TASKS),
8687
decode_responses=False,
88+
client_name="example_app",
8789
)
8890
self._manager = DeferredManager(
8991
rabbit_settings,

0 commit comments

Comments
 (0)