Skip to content

Commit 66d69a6

Browse files
✨ Workspaces / Folders v2 🗃️🚨 (#6248)
1 parent 161599b commit 66d69a6

File tree

83 files changed

+4300
-1217
lines changed

Some content is hidden

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

83 files changed

+4300
-1217
lines changed

api/specs/web-server/_folders.py

Lines changed: 5 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,18 @@
1010
from typing import Annotated
1111

1212
from fastapi import APIRouter, Depends, Query, status
13-
from models_library.api_schemas_webserver.folders import (
13+
from models_library.api_schemas_webserver.folders_v2 import (
1414
CreateFolderBodyParams,
1515
FolderGet,
1616
PutFolderBodyParams,
1717
)
18+
from models_library.folders import FolderID
1819
from models_library.generics import Envelope
1920
from models_library.rest_pagination import PageQueryParameters
21+
from models_library.workspaces import WorkspaceID
2022
from pydantic import Json
2123
from simcore_service_webserver._meta import API_VTAG
2224
from simcore_service_webserver.folders._folders_handlers import FoldersPathParams
23-
from simcore_service_webserver.folders._groups_api import FolderGroupGet
24-
from simcore_service_webserver.folders._groups_handlers import (
25-
_FoldersGroupsBodyParams,
26-
_FoldersGroupsPathParams,
27-
)
2825

2926
router = APIRouter(
3027
prefix=f"/{API_VTAG}",
@@ -51,6 +48,8 @@ async def create_folder(_body: CreateFolderBodyParams):
5148
)
5249
async def list_folders(
5350
params: Annotated[PageQueryParameters, Depends()],
51+
folder_id: FolderID | None = None,
52+
workspace_id: WorkspaceID | None = None,
5453
order_by: Annotated[
5554
Json,
5655
Query(
@@ -86,45 +85,3 @@ async def replace_folder(
8685
)
8786
async def delete_folder(_path: Annotated[FoldersPathParams, Depends()]):
8887
...
89-
90-
91-
### Folders groups
92-
93-
94-
@router.post(
95-
"/folders/{folder_id}/groups/{group_id}",
96-
response_model=Envelope[FolderGroupGet],
97-
status_code=status.HTTP_201_CREATED,
98-
)
99-
async def create_folder_group(
100-
_path: Annotated[_FoldersGroupsPathParams, Depends()],
101-
_body: _FoldersGroupsBodyParams,
102-
):
103-
...
104-
105-
106-
@router.get(
107-
"/folders/{folder_id}/groups",
108-
response_model=Envelope[list[FolderGroupGet]],
109-
)
110-
async def list_folder_groups(_path: Annotated[FoldersPathParams, Depends()]):
111-
...
112-
113-
114-
@router.put(
115-
"/folders/{folder_id}/groups/{group_id}",
116-
response_model=Envelope[FolderGroupGet],
117-
)
118-
async def replace_folder_group(
119-
_path: Annotated[_FoldersGroupsPathParams, Depends()],
120-
_body: _FoldersGroupsBodyParams,
121-
):
122-
...
123-
124-
125-
@router.delete(
126-
"/folders/{folder_id}/groups/{group_id}",
127-
status_code=status.HTTP_204_NO_CONTENT,
128-
)
129-
async def delete_folder_group(_path: Annotated[_FoldersGroupsPathParams, Depends()]):
130-
...
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
""" Helper script to generate OAS automatically
2+
"""
3+
4+
# pylint: disable=redefined-outer-name
5+
# pylint: disable=unused-argument
6+
# pylint: disable=unused-variable
7+
# pylint: disable=too-many-arguments
8+
9+
10+
from fastapi import APIRouter, status
11+
from models_library.api_schemas_webserver.workspaces import (
12+
CreateWorkspaceBodyParams,
13+
PutWorkspaceBodyParams,
14+
WorkspaceGet,
15+
)
16+
from models_library.generics import Envelope
17+
from models_library.users import GroupID
18+
from models_library.workspaces import WorkspaceID
19+
from simcore_service_webserver._meta import API_VTAG
20+
from simcore_service_webserver.workspaces._groups_api import WorkspaceGroupGet
21+
from simcore_service_webserver.workspaces._groups_handlers import (
22+
_WorkspacesGroupsBodyParams,
23+
)
24+
25+
router = APIRouter(
26+
prefix=f"/{API_VTAG}",
27+
tags=[
28+
"workspaces",
29+
],
30+
)
31+
32+
### Workspaces
33+
34+
35+
@router.post(
36+
"/workspaces",
37+
response_model=Envelope[WorkspaceGet],
38+
status_code=status.HTTP_201_CREATED,
39+
)
40+
async def create_workspace(body: CreateWorkspaceBodyParams):
41+
...
42+
43+
44+
@router.get(
45+
"/workspaces",
46+
response_model=Envelope[list[WorkspaceGet]],
47+
)
48+
async def list_workspaces():
49+
...
50+
51+
52+
@router.get(
53+
"/workspaces/{workspace_id}",
54+
response_model=Envelope[WorkspaceGet],
55+
)
56+
async def get_workspace(workspace_id: WorkspaceID):
57+
...
58+
59+
60+
@router.put(
61+
"/workspaces/{workspace_id}",
62+
response_model=Envelope[WorkspaceGet],
63+
)
64+
async def replace_workspace(workspace_id: WorkspaceID, body: PutWorkspaceBodyParams):
65+
...
66+
67+
68+
@router.delete(
69+
"/workspaces/{workspace_id}",
70+
status_code=status.HTTP_204_NO_CONTENT,
71+
)
72+
async def delete_workspace(workspace_id: WorkspaceID):
73+
...
74+
75+
76+
### Workspaces groups
77+
78+
79+
@router.post(
80+
"/workspaces/{workspace_id}/groups/{group_id}",
81+
response_model=Envelope[WorkspaceGroupGet],
82+
status_code=status.HTTP_201_CREATED,
83+
)
84+
async def create_workspace_group(
85+
workspace_id: WorkspaceID, group_id: GroupID, body: _WorkspacesGroupsBodyParams
86+
):
87+
...
88+
89+
90+
@router.get(
91+
"/workspaces/{workspace_id}/groups",
92+
response_model=Envelope[list[WorkspaceGroupGet]],
93+
)
94+
async def list_workspace_groups(workspace_id: WorkspaceID):
95+
...
96+
97+
98+
@router.put(
99+
"/workspaces/{workspace_id}/groups/{group_id}",
100+
response_model=Envelope[WorkspaceGroupGet],
101+
)
102+
async def replace_workspace_group(
103+
workspace_id: WorkspaceID, group_id: GroupID, body: _WorkspacesGroupsBodyParams
104+
):
105+
...
106+
107+
108+
@router.delete(
109+
"/workspaces/{workspace_id}/groups/{group_id}",
110+
status_code=status.HTTP_204_NO_CONTENT,
111+
)
112+
async def delete_workspace_group(workspace_id: WorkspaceID, group_id: GroupID):
113+
...

api/specs/web-server/openapi.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"_users",
5151
"_version_control",
5252
"_wallets",
53+
"_workspaces",
5354
)
5455
]
5556

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from pydantic import BaseModel, Extra, Field
2+
3+
4+
class AccessRights(BaseModel):
5+
read: bool = Field(..., description="has read access")
6+
write: bool = Field(..., description="has write access")
7+
delete: bool = Field(..., description="has deletion rights")
8+
9+
class Config:
10+
extra = Extra.forbid
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from datetime import datetime
2+
from typing import NamedTuple
3+
4+
from models_library.basic_types import IDStr
5+
from models_library.folders import FolderID
6+
from models_library.users import GroupID
7+
from models_library.utils.common_validators import null_or_none_str_to_none_validator
8+
from models_library.workspaces import WorkspaceID
9+
from pydantic import Extra, PositiveInt, validator
10+
11+
from ._base import InputSchema, OutputSchema
12+
13+
14+
class FolderGet(OutputSchema):
15+
folder_id: FolderID
16+
parent_folder_id: FolderID | None = None
17+
name: str
18+
created_at: datetime
19+
modified_at: datetime
20+
owner: GroupID
21+
22+
23+
class FolderGetPage(NamedTuple):
24+
items: list[FolderGet]
25+
total: PositiveInt
26+
27+
28+
class CreateFolderBodyParams(InputSchema):
29+
name: IDStr
30+
parent_folder_id: FolderID | None = None
31+
workspace_id: WorkspaceID | None = None
32+
33+
class Config:
34+
extra = Extra.forbid
35+
36+
_null_or_none_str_to_none_validator = validator(
37+
"parent_folder_id", allow_reuse=True, pre=True
38+
)(null_or_none_str_to_none_validator)
39+
40+
_null_or_none_str_to_none_validator2 = validator(
41+
"workspace_id", allow_reuse=True, pre=True
42+
)(null_or_none_str_to_none_validator)
43+
44+
45+
class PutFolderBodyParams(InputSchema):
46+
name: IDStr
47+
parent_folder_id: FolderID | None
48+
49+
class Config:
50+
extra = Extra.forbid
51+
52+
_null_or_none_str_to_none_validator = validator(
53+
"parent_folder_id", allow_reuse=True, pre=True
54+
)(null_or_none_str_to_none_validator)

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from typing import Any, Literal, TypeAlias
99

10+
from models_library.workspaces import WorkspaceID
1011
from pydantic import Field, validator
1112

1213
from ..api_schemas_long_running_tasks.tasks import TaskGet
@@ -39,9 +40,10 @@ class ProjectCreateNew(InputSchema):
3940
tags: list[int] = Field(default_factory=list)
4041
classifiers: list[ClassifierID] = Field(default_factory=list)
4142
ui: StudyUI | None = None
43+
workspace_id: WorkspaceID | None = None
4244

4345
_empty_is_none = validator(
44-
"uuid", "thumbnail", "description", allow_reuse=True, pre=True
46+
"uuid", "thumbnail", "description", "workspace_id", allow_reuse=True, pre=True
4547
)(empty_str_to_none_pre_validator)
4648

4749

@@ -74,6 +76,7 @@ class ProjectGet(OutputSchema):
7476
quality: dict[str, Any] = {}
7577
dev: dict | None
7678
permalink: ProjectPermalink = FieldNotRequired()
79+
workspace_id: WorkspaceID | None
7780

7881
_empty_description = validator("description", allow_reuse=True, pre=True)(
7982
none_to_empty_str_pre_validator
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from datetime import datetime
2+
from typing import NamedTuple
3+
4+
from models_library.basic_types import IDStr
5+
from models_library.users import GroupID
6+
from models_library.workspaces import WorkspaceID
7+
from pydantic import Extra, PositiveInt
8+
9+
from ..access_rights import AccessRights
10+
from ._base import InputSchema, OutputSchema
11+
12+
13+
class WorkspaceGet(OutputSchema):
14+
workspace_id: WorkspaceID
15+
name: str
16+
description: str | None
17+
thumbnail: str | None
18+
created_at: datetime
19+
modified_at: datetime
20+
my_access_rights: AccessRights
21+
access_rights: dict[GroupID, AccessRights]
22+
23+
24+
class WorkspaceGetPage(NamedTuple):
25+
items: list[WorkspaceGet]
26+
total: PositiveInt
27+
28+
29+
class CreateWorkspaceBodyParams(InputSchema):
30+
name: str
31+
description: str | None = None
32+
thumbnail: str | None = None
33+
34+
class Config:
35+
extra = Extra.forbid
36+
37+
38+
class PutWorkspaceBodyParams(InputSchema):
39+
name: IDStr
40+
description: str | None = None
41+
thumbnail: str | None = None
42+
43+
class Config:
44+
extra = Extra.forbid
Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,36 @@
1+
from datetime import datetime
12
from typing import TypeAlias
23

3-
from pydantic import PositiveInt
4+
from models_library.users import GroupID, UserID
5+
from models_library.workspaces import WorkspaceID
6+
from pydantic import BaseModel, Field, PositiveInt
47

58
FolderID: TypeAlias = PositiveInt
9+
10+
11+
#
12+
# DB
13+
#
14+
15+
16+
class FolderDB(BaseModel):
17+
folder_id: FolderID
18+
name: str
19+
parent_folder_id: FolderID | None
20+
created_by_gid: GroupID = Field(
21+
...,
22+
description="GID of the group that owns this wallet",
23+
)
24+
created: datetime = Field(
25+
...,
26+
description="Timestamp on creation",
27+
)
28+
modified: datetime = Field(
29+
...,
30+
description="Timestamp of last modification",
31+
)
32+
user_id: UserID | None
33+
workspace_id: WorkspaceID | None
34+
35+
class Config:
36+
orm_mode = True

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from typing import Any, Final, TypeAlias
99
from uuid import UUID
1010

11+
from models_library.workspaces import WorkspaceID
1112
from pydantic import BaseModel, ConstrainedStr, Extra, Field, validator
1213

1314
from .basic_regex import DATE_RE, UUID_RE_BASE
@@ -173,6 +174,12 @@ class Project(BaseProjectModel):
173174
default=None, description="object used for development purposes only"
174175
)
175176

177+
workspace_id: WorkspaceID | None = Field(
178+
default=None,
179+
description="To which workspace project belongs. If None, belongs to private user workspace.",
180+
alias="workspaceId",
181+
)
182+
176183
class Config:
177184
description = "Document that stores metadata, pipeline and UI setup of a study"
178185
title = "osparc-simcore project"

0 commit comments

Comments
 (0)