Skip to content

Commit a21d245

Browse files
matusdrobuliak66Matus Drobuliak
andauthored
✨ introduce conversations (🗃️) (#7591)
Co-authored-by: Matus Drobuliak <[email protected]>
1 parent d6deede commit a21d245

File tree

29 files changed

+2889
-105
lines changed

29 files changed

+2889
-105
lines changed

api/specs/web-server/_projects_comments.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from typing import Literal
1212

1313
from _common import assert_handler_signature_against_model
14-
from fastapi import APIRouter
14+
from fastapi import APIRouter, status
1515
from models_library.generics import Envelope
1616
from models_library.projects import ProjectID
1717
from models_library.projects_comments import CommentID, ProjectsCommentsAPI
@@ -41,7 +41,8 @@
4141
"/projects/{project_uuid}/comments",
4242
response_model=Envelope[dict[Literal["comment_id"], CommentID]],
4343
description="Create a new comment for a specific project. The request body should contain the comment contents and user information.",
44-
status_code=201,
44+
status_code=status.HTTP_201_CREATED,
45+
deprecated=True,
4546
)
4647
async def create_project_comment(
4748
project_uuid: ProjectID, body: _ProjectCommentsBodyParams
@@ -57,6 +58,7 @@ async def create_project_comment(
5758
"/projects/{project_uuid}/comments",
5859
response_model=Envelope[list[ProjectsCommentsAPI]],
5960
description="Retrieve all comments for a specific project.",
61+
deprecated=True,
6062
)
6163
async def list_project_comments(
6264
project_uuid: ProjectID, limit: int = 20, offset: NonNegativeInt = 0
@@ -72,6 +74,7 @@ async def list_project_comments(
7274
"/projects/{project_uuid}/comments/{comment_id}",
7375
response_model=Envelope[ProjectsCommentsAPI],
7476
description="Update the contents of a specific comment for a project. The request body should contain the updated comment contents.",
77+
deprecated=True,
7578
)
7679
async def update_project_comment(
7780
project_uuid: ProjectID,
@@ -88,7 +91,8 @@ async def update_project_comment(
8891
@router.delete(
8992
"/projects/{project_uuid}/comments/{comment_id}",
9093
description="Delete a specific comment associated with a project.",
91-
status_code=204,
94+
status_code=status.HTTP_204_NO_CONTENT,
95+
deprecated=True,
9296
)
9397
async def delete_project_comment(project_uuid: ProjectID, comment_id: CommentID): ...
9498

@@ -102,6 +106,7 @@ async def delete_project_comment(project_uuid: ProjectID, comment_id: CommentID)
102106
"/projects/{project_uuid}/comments/{comment_id}",
103107
response_model=Envelope[ProjectsCommentsAPI],
104108
description="Retrieve a specific comment by its ID within a project.",
109+
deprecated=True,
105110
)
106111
async def get_project_comment(project_uuid: ProjectID, comment_id: CommentID): ...
107112

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
"""Helper script to automatically generate OAS
2+
3+
This OAS are the source of truth
4+
"""
5+
6+
# pylint: disable=redefined-outer-name
7+
# pylint: disable=unused-argument
8+
# pylint: disable=unused-variable
9+
# pylint: disable=too-many-arguments
10+
11+
12+
from typing import Annotated
13+
14+
from fastapi import APIRouter, Depends, status
15+
from models_library.api_schemas_webserver.projects_conversations import (
16+
ConversationMessageRestGet,
17+
ConversationRestGet,
18+
)
19+
from models_library.generics import Envelope
20+
from models_library.rest_pagination import Page
21+
from simcore_service_webserver._meta import API_VTAG
22+
from simcore_service_webserver.projects._controller._rest_schemas import (
23+
ProjectPathParams,
24+
)
25+
from simcore_service_webserver.projects._controller.conversations_rest import (
26+
_ListProjectConversationMessagesQueryParams,
27+
_ListProjectConversationsQueryParams,
28+
_ProjectConversationMessagesCreateBodyParams,
29+
_ProjectConversationMessagesPutBodyParams,
30+
_ProjectConversationsCreateBodyParams,
31+
_ProjectConversationsMessagesPathParams,
32+
_ProjectConversationsPathParams,
33+
_ProjectConversationsPutBodyParams,
34+
)
35+
36+
router = APIRouter(
37+
prefix=f"/{API_VTAG}",
38+
tags=[
39+
"projects",
40+
"conversations",
41+
],
42+
)
43+
44+
45+
#
46+
# API entrypoints PROJECTS/*/CONVERSATIONS/*
47+
#
48+
49+
50+
@router.post(
51+
"/projects/{project_id}/conversations",
52+
response_model=Envelope[ConversationRestGet],
53+
status_code=status.HTTP_201_CREATED,
54+
)
55+
async def create_project_conversation(
56+
_params: Annotated[ProjectPathParams, Depends()],
57+
_body: _ProjectConversationsCreateBodyParams,
58+
): ...
59+
60+
61+
@router.get(
62+
"/projects/{project_id}/conversations",
63+
response_model=Page[ConversationRestGet],
64+
)
65+
async def list_project_conversations(
66+
_params: Annotated[ProjectPathParams, Depends()],
67+
_query: Annotated[_ListProjectConversationsQueryParams, Depends()],
68+
): ...
69+
70+
71+
@router.put(
72+
"/projects/{project_id}/conversations/{conversation_id}",
73+
response_model=Envelope[ConversationRestGet],
74+
)
75+
async def update_project_conversation(
76+
_params: Annotated[_ProjectConversationsPathParams, Depends()],
77+
_body: _ProjectConversationsPutBodyParams,
78+
): ...
79+
80+
81+
@router.delete(
82+
"/projects/{project_id}/conversations/{conversation_id}",
83+
status_code=status.HTTP_204_NO_CONTENT,
84+
)
85+
async def delete_project_conversation(
86+
_params: Annotated[_ProjectConversationsPathParams, Depends()],
87+
): ...
88+
89+
90+
@router.get(
91+
"/projects/{project_id}/conversations/{conversation_id}",
92+
response_model=Envelope[ConversationRestGet],
93+
)
94+
async def get_project_conversation(
95+
_params: Annotated[_ProjectConversationsPathParams, Depends()],
96+
): ...
97+
98+
99+
### Conversation Messages
100+
101+
102+
@router.post(
103+
"/projects/{project_id}/conversations/{conversation_id}/messages",
104+
response_model=Envelope[ConversationMessageRestGet],
105+
status_code=status.HTTP_201_CREATED,
106+
)
107+
async def create_project_conversation_message(
108+
_params: Annotated[_ProjectConversationsPathParams, Depends()],
109+
_body: _ProjectConversationMessagesCreateBodyParams,
110+
): ...
111+
112+
113+
@router.get(
114+
"/projects/{project_id}/conversations/{conversation_id}/messages",
115+
response_model=Page[ConversationMessageRestGet],
116+
)
117+
async def list_project_conversation_messages(
118+
_params: Annotated[_ProjectConversationsPathParams, Depends()],
119+
_query: Annotated[_ListProjectConversationMessagesQueryParams, Depends()],
120+
): ...
121+
122+
123+
@router.put(
124+
"/projects/{project_id}/conversations/{conversation_id}/messages/{message_id}",
125+
response_model=Envelope[ConversationMessageRestGet],
126+
)
127+
async def update_project_conversation_message(
128+
_params: Annotated[_ProjectConversationsMessagesPathParams, Depends()],
129+
_body: _ProjectConversationMessagesPutBodyParams,
130+
): ...
131+
132+
133+
@router.delete(
134+
"/projects/{project_id}/conversations/{conversation_id}/messages/{message_id}",
135+
status_code=status.HTTP_204_NO_CONTENT,
136+
)
137+
async def delete_project_conversation_message(
138+
_params: Annotated[_ProjectConversationsMessagesPathParams, Depends()],
139+
): ...
140+
141+
142+
@router.get(
143+
"/projects/{project_id}/conversations/{conversation_id}/messages/{message_id}",
144+
response_model=Envelope[ConversationMessageRestGet],
145+
)
146+
async def get_project_conversation_message(
147+
_params: Annotated[_ProjectConversationsMessagesPathParams, Depends()],
148+
): ...

api/specs/web-server/openapi.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"_projects",
4646
"_projects_access_rights",
4747
"_projects_comments",
48+
"_projects_conversations",
4849
"_projects_folders",
4950
"_projects_metadata",
5051
"_projects_nodes",
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
from datetime import datetime
2+
from typing import Annotated, Self
3+
4+
from pydantic import Field
5+
6+
from ..conversations import (
7+
ConversationGetDB,
8+
ConversationID,
9+
ConversationMessageGetDB,
10+
ConversationMessageID,
11+
ConversationMessageType,
12+
ConversationType,
13+
)
14+
from ..groups import GroupID
15+
from ..products import ProductName
16+
from ..projects import ProjectID
17+
from ._base import InputSchema, OutputSchema
18+
19+
### PROJECT CONVERSATION -------------------------------------------------------------------
20+
21+
22+
class ConversationRestGet(OutputSchema):
23+
conversation_id: ConversationID
24+
product_name: ProductName
25+
name: Annotated[str, Field(max_length=50)]
26+
project_uuid: ProjectID | None
27+
user_group_id: GroupID
28+
type: ConversationType
29+
created: datetime
30+
modified: datetime
31+
32+
@classmethod
33+
def from_domain_model(cls, domain: ConversationGetDB) -> Self:
34+
return cls(
35+
conversation_id=domain.conversation_id,
36+
product_name=domain.product_name,
37+
name=domain.name,
38+
project_uuid=domain.project_uuid,
39+
user_group_id=domain.user_group_id,
40+
type=domain.type,
41+
created=domain.created,
42+
modified=domain.modified,
43+
)
44+
45+
46+
class ConversationPatch(InputSchema):
47+
name: str | None = None
48+
49+
50+
### PROJECT CONVERSATION MESSAGES ---------------------------------------------------------------
51+
52+
53+
class ConversationMessageRestGet(OutputSchema):
54+
message_id: ConversationMessageID
55+
conversation_id: ConversationID
56+
user_group_id: GroupID
57+
content: Annotated[str, Field(max_length=4096)]
58+
type: ConversationMessageType
59+
created: datetime
60+
modified: datetime
61+
62+
@classmethod
63+
def from_domain_model(cls, domain: ConversationMessageGetDB) -> Self:
64+
return cls(
65+
message_id=domain.message_id,
66+
conversation_id=domain.conversation_id,
67+
user_group_id=domain.user_group_id,
68+
content=domain.content,
69+
type=domain.type,
70+
created=domain.created,
71+
modified=domain.modified,
72+
)
73+
74+
75+
class ConversationMessagePatch(InputSchema):
76+
content: str | None = None
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
from datetime import datetime
2+
from enum import auto
3+
from typing import TypeAlias
4+
from uuid import UUID
5+
6+
from models_library.groups import GroupID
7+
from models_library.projects import ProjectID
8+
from pydantic import BaseModel, ConfigDict
9+
10+
from .products import ProductName
11+
from .utils.enums import StrAutoEnum
12+
13+
ConversationID: TypeAlias = UUID
14+
ConversationMessageID: TypeAlias = UUID
15+
16+
17+
class ConversationType(StrAutoEnum):
18+
PROJECT_STATIC = auto() # Static conversation for the project
19+
PROJECT_ANNOTATION = (
20+
auto() # Something like sticky note, can be located anywhere in the pipeline UI
21+
)
22+
23+
24+
class ConversationMessageType(StrAutoEnum):
25+
MESSAGE = auto()
26+
NOTIFICATION = (
27+
auto() # Special type of message used for storing notifications in the conversation
28+
)
29+
30+
31+
#
32+
# DB
33+
#
34+
35+
36+
class ConversationGetDB(BaseModel):
37+
conversation_id: ConversationID
38+
product_name: ProductName
39+
name: str
40+
project_uuid: ProjectID | None
41+
user_group_id: GroupID
42+
type: ConversationType
43+
44+
# states
45+
created: datetime
46+
modified: datetime
47+
48+
model_config = ConfigDict(from_attributes=True)
49+
50+
51+
class ConversationMessageGetDB(BaseModel):
52+
message_id: ConversationMessageID
53+
conversation_id: ConversationID
54+
user_group_id: GroupID
55+
content: str
56+
type: ConversationMessageType
57+
58+
# states
59+
created: datetime
60+
modified: datetime
61+
62+
model_config = ConfigDict(from_attributes=True)
63+
64+
65+
class ConversationPatchDB(BaseModel):
66+
name: str | None = None
67+
68+
69+
class ConversationMessagePatchDB(BaseModel):
70+
content: str | None = None

0 commit comments

Comments
 (0)