Skip to content

Commit 184ad5e

Browse files
authored
Allow workspace operations to work without caring about the case (#736)
* Allow workspace operations to work without caring about the case Ultimately all operations will work regardless of the case. Though the final storage format will be lowercase. Signed-off-by: Juan Antonio Osorio <[email protected]> * Fix unit tests Signed-off-by: Juan Antonio Osorio <[email protected]> --------- Signed-off-by: Juan Antonio Osorio <[email protected]>
1 parent 2414395 commit 184ad5e

File tree

7 files changed

+78
-51
lines changed

7 files changed

+78
-51
lines changed

src/codegate/api/v1.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ async def list_workspaces() -> v1_models.ListWorkspacesResponse:
2727
"""List all workspaces."""
2828
wslist = await wscrud.get_workspaces()
2929

30-
resp = v1_models.ListWorkspacesResponse.from_db_workspaces_active(wslist)
30+
resp = v1_models.ListWorkspacesResponse.from_db_workspaces_with_sessioninfo(wslist)
3131

3232
return resp
3333

src/codegate/api/v1_models.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,18 @@ class ListWorkspacesResponse(pydantic.BaseModel):
2323
workspaces: list[Workspace]
2424

2525
@classmethod
26-
def from_db_workspaces_active(
27-
cls, db_workspaces: List[db_models.WorkspaceActive]
26+
def from_db_workspaces_with_sessioninfo(
27+
cls, db_workspaces: List[db_models.WorkspaceWithSessionInfo]
2828
) -> "ListWorkspacesResponse":
2929
return cls(
3030
workspaces=[
31-
Workspace(name=ws.name, is_active=ws.active_workspace_id is not None)
32-
for ws in db_workspaces
31+
Workspace(name=ws.name, is_active=ws.session_id is not None) for ws in db_workspaces
3332
]
3433
)
3534

3635
@classmethod
3736
def from_db_workspaces(
38-
cls, db_workspaces: List[db_models.Workspace]
37+
cls, db_workspaces: List[db_models.WorkspaceRow]
3938
) -> "ListWorkspacesResponse":
4039
return cls(workspaces=[Workspace(name=ws.name, is_active=False) for ws in db_workspaces])
4140

src/codegate/db/connection.py

+22-19
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@
1919
Alert,
2020
GetAlertsWithPromptAndOutputRow,
2121
GetPromptWithOutputsRow,
22+
GetWorkspaceByNameConditions,
2223
Output,
2324
Prompt,
2425
Session,
25-
Workspace,
26-
WorkspaceActive,
26+
WorkspaceRow,
27+
WorkspaceWithSessionInfo,
2728
)
2829
from codegate.pipeline.base import PipelineContext
2930

@@ -263,15 +264,17 @@ async def record_context(self, context: Optional[PipelineContext]) -> None:
263264
except Exception as e:
264265
logger.error(f"Failed to record context: {context}.", error=str(e))
265266

266-
async def add_workspace(self, workspace_name: str) -> Workspace:
267+
async def add_workspace(self, workspace_name: str) -> WorkspaceRow:
267268
"""Add a new workspace to the DB.
268269
269270
This handles validation and insertion of a new workspace.
270271
271272
It may raise a ValidationError if the workspace name is invalid.
272273
or a AlreadyExistsError if the workspace already exists.
273274
"""
274-
workspace = Workspace(id=str(uuid.uuid4()), name=workspace_name, custom_instructions=None)
275+
workspace = WorkspaceRow(
276+
id=str(uuid.uuid4()), name=workspace_name, custom_instructions=None
277+
)
275278
sql = text(
276279
"""
277280
INSERT INTO workspaces (id, name)
@@ -289,7 +292,7 @@ async def add_workspace(self, workspace_name: str) -> Workspace:
289292
raise AlreadyExistsError(f"Workspace {workspace_name} already exists.")
290293
return added_workspace
291294

292-
async def update_workspace(self, workspace: Workspace) -> Workspace:
295+
async def update_workspace(self, workspace: WorkspaceRow) -> WorkspaceRow:
293296
sql = text(
294297
"""
295298
UPDATE workspaces SET
@@ -319,7 +322,7 @@ async def update_session(self, session: Session) -> Optional[Session]:
319322
active_session = await self._execute_update_pydantic_model(session, sql, should_raise=True)
320323
return active_session
321324

322-
async def soft_delete_workspace(self, workspace: Workspace) -> Optional[Workspace]:
325+
async def soft_delete_workspace(self, workspace: WorkspaceRow) -> Optional[WorkspaceRow]:
323326
sql = text(
324327
"""
325328
UPDATE workspaces
@@ -333,7 +336,7 @@ async def soft_delete_workspace(self, workspace: Workspace) -> Optional[Workspac
333336
)
334337
return deleted_workspace
335338

336-
async def hard_delete_workspace(self, workspace: Workspace) -> Optional[Workspace]:
339+
async def hard_delete_workspace(self, workspace: WorkspaceRow) -> Optional[WorkspaceRow]:
337340
sql = text(
338341
"""
339342
DELETE FROM workspaces
@@ -346,7 +349,7 @@ async def hard_delete_workspace(self, workspace: Workspace) -> Optional[Workspac
346349
)
347350
return deleted_workspace
348351

349-
async def recover_workspace(self, workspace: Workspace) -> Optional[Workspace]:
352+
async def recover_workspace(self, workspace: WorkspaceRow) -> Optional[WorkspaceRow]:
350353
sql = text(
351354
"""
352355
UPDATE workspaces
@@ -460,20 +463,20 @@ async def get_alerts_with_prompt_and_output(
460463
)
461464
return prompts
462465

463-
async def get_workspaces(self) -> List[WorkspaceActive]:
466+
async def get_workspaces(self) -> List[WorkspaceWithSessionInfo]:
464467
sql = text(
465468
"""
466469
SELECT
467-
w.id, w.name, s.active_workspace_id
470+
w.id, w.name, s.id as session_id
468471
FROM workspaces w
469472
LEFT JOIN sessions s ON w.id = s.active_workspace_id
470473
WHERE w.deleted_at IS NULL
471474
"""
472475
)
473-
workspaces = await self._execute_select_pydantic_model(WorkspaceActive, sql)
476+
workspaces = await self._execute_select_pydantic_model(WorkspaceWithSessionInfo, sql)
474477
return workspaces
475478

476-
async def get_archived_workspaces(self) -> List[Workspace]:
479+
async def get_archived_workspaces(self) -> List[WorkspaceRow]:
477480
sql = text(
478481
"""
479482
SELECT
@@ -483,10 +486,10 @@ async def get_archived_workspaces(self) -> List[Workspace]:
483486
ORDER BY deleted_at DESC
484487
"""
485488
)
486-
workspaces = await self._execute_select_pydantic_model(Workspace, sql)
489+
workspaces = await self._execute_select_pydantic_model(WorkspaceRow, sql)
487490
return workspaces
488491

489-
async def get_workspace_by_name(self, name: str) -> Optional[Workspace]:
492+
async def get_workspace_by_name(self, name: str) -> Optional[WorkspaceRow]:
490493
sql = text(
491494
"""
492495
SELECT
@@ -495,13 +498,13 @@ async def get_workspace_by_name(self, name: str) -> Optional[Workspace]:
495498
WHERE name = :name AND deleted_at IS NULL
496499
"""
497500
)
498-
conditions = {"name": name}
501+
conditions = GetWorkspaceByNameConditions(name=name).get_conditions()
499502
workspaces = await self._exec_select_conditions_to_pydantic(
500-
Workspace, sql, conditions, should_raise=True
503+
WorkspaceRow, sql, conditions, should_raise=True
501504
)
502505
return workspaces[0] if workspaces else None
503506

504-
async def get_archived_workspace_by_name(self, name: str) -> Optional[Workspace]:
507+
async def get_archived_workspace_by_name(self, name: str) -> Optional[WorkspaceRow]:
505508
sql = text(
506509
"""
507510
SELECT
@@ -510,9 +513,9 @@ async def get_archived_workspace_by_name(self, name: str) -> Optional[Workspace]
510513
WHERE name = :name AND deleted_at IS NOT NULL
511514
"""
512515
)
513-
conditions = {"name": name}
516+
conditions = GetWorkspaceByNameConditions(name=name).get_conditions()
514517
workspaces = await self._exec_select_conditions_to_pydantic(
515-
Workspace, sql, conditions, should_raise=True
518+
WorkspaceRow, sql, conditions, should_raise=True
516519
)
517520
return workspaces[0] if workspaces else None
518521

src/codegate/db/models.py

+30-7
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,34 @@ class Prompt(BaseModel):
3030
workspace_id: Optional[str]
3131

3232

33-
WorskpaceNameStr = Annotated[
33+
WorkspaceNameStr = Annotated[
3434
str,
3535
StringConstraints(
3636
strip_whitespace=True, to_lower=True, pattern=r"^[a-zA-Z0-9_-]+$", strict=True
3737
),
3838
]
3939

4040

41-
class Workspace(BaseModel):
41+
class WorkspaceRow(BaseModel):
42+
"""A workspace row entry.
43+
44+
Since our model currently includes instructions
45+
in the same table, this is returned as a single
46+
object.
47+
"""
48+
4249
id: str
43-
name: WorskpaceNameStr
50+
name: WorkspaceNameStr
4451
custom_instructions: Optional[str]
4552

4653

54+
class GetWorkspaceByNameConditions(BaseModel):
55+
name: WorkspaceNameStr
56+
57+
def get_conditions(self):
58+
return {"name": self.name}
59+
60+
4761
class Session(BaseModel):
4862
id: str
4963
active_workspace_id: str
@@ -81,15 +95,24 @@ class GetPromptWithOutputsRow(BaseModel):
8195
output_timestamp: Optional[Any]
8296

8397

84-
class WorkspaceActive(BaseModel):
98+
class WorkspaceWithSessionInfo(BaseModel):
99+
"""Returns a workspace ID with an optional
100+
session ID. If the session ID is None, then
101+
the workspace is not active.
102+
"""
103+
85104
id: str
86-
name: str
87-
active_workspace_id: Optional[str]
105+
name: WorkspaceNameStr
106+
session_id: Optional[str]
88107

89108

90109
class ActiveWorkspace(BaseModel):
110+
"""Returns a full active workspace object with the
111+
with the session information.
112+
"""
113+
91114
id: str
92-
name: str
115+
name: WorkspaceNameStr
93116
custom_instructions: Optional[str]
94117
session_id: str
95118
last_update: datetime.datetime

src/codegate/pipeline/cli/commands.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ async def _list_workspaces(self, flags: Dict[str, str], args: List[str]) -> str:
168168
respond_str = ""
169169
for workspace in workspaces:
170170
respond_str += f"- {workspace.name}"
171-
if workspace.active_workspace_id:
171+
if workspace.session_id:
172172
respond_str += " **(active)**"
173173
respond_str += "\n"
174174
return respond_str

src/codegate/workspaces/crud.py

+12-10
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from typing import List, Optional, Tuple
33

44
from codegate.db.connection import DbReader, DbRecorder
5-
from codegate.db.models import ActiveWorkspace, Session, Workspace, WorkspaceActive
5+
from codegate.db.models import ActiveWorkspace, Session, WorkspaceRow, WorkspaceWithSessionInfo
66

77

88
class WorkspaceCrudError(Exception):
@@ -28,7 +28,7 @@ class WorkspaceCrud:
2828
def __init__(self):
2929
self._db_reader = DbReader()
3030

31-
async def add_workspace(self, new_workspace_name: str) -> Workspace:
31+
async def add_workspace(self, new_workspace_name: str) -> WorkspaceRow:
3232
"""
3333
Add a workspace
3434
@@ -43,7 +43,9 @@ async def add_workspace(self, new_workspace_name: str) -> Workspace:
4343
workspace_created = await db_recorder.add_workspace(new_workspace_name)
4444
return workspace_created
4545

46-
async def rename_workspace(self, old_workspace_name: str, new_workspace_name: str) -> Workspace:
46+
async def rename_workspace(
47+
self, old_workspace_name: str, new_workspace_name: str
48+
) -> WorkspaceRow:
4749
"""
4850
Rename a workspace
4951
@@ -65,19 +67,19 @@ async def rename_workspace(self, old_workspace_name: str, new_workspace_name: st
6567
if not ws:
6668
raise WorkspaceDoesNotExistError(f"Workspace {old_workspace_name} does not exist.")
6769
db_recorder = DbRecorder()
68-
new_ws = Workspace(
70+
new_ws = WorkspaceRow(
6971
id=ws.id, name=new_workspace_name, custom_instructions=ws.custom_instructions
7072
)
7173
workspace_renamed = await db_recorder.update_workspace(new_ws)
7274
return workspace_renamed
7375

74-
async def get_workspaces(self) -> List[WorkspaceActive]:
76+
async def get_workspaces(self) -> List[WorkspaceWithSessionInfo]:
7577
"""
7678
Get all workspaces
7779
"""
7880
return await self._db_reader.get_workspaces()
7981

80-
async def get_archived_workspaces(self) -> List[Workspace]:
82+
async def get_archived_workspaces(self) -> List[WorkspaceRow]:
8183
"""
8284
Get all archived workspaces
8385
"""
@@ -91,7 +93,7 @@ async def get_active_workspace(self) -> Optional[ActiveWorkspace]:
9193

9294
async def _is_workspace_active(
9395
self, workspace_name: str
94-
) -> Tuple[bool, Optional[Session], Optional[Workspace]]:
96+
) -> Tuple[bool, Optional[Session], Optional[WorkspaceRow]]:
9597
"""
9698
Check if the workspace is active alongside the session and workspace objects
9799
"""
@@ -137,13 +139,13 @@ async def recover_workspace(self, workspace_name: str):
137139

138140
async def update_workspace_custom_instructions(
139141
self, workspace_name: str, custom_instr_lst: List[str]
140-
) -> Workspace:
142+
) -> WorkspaceRow:
141143
selected_workspace = await self._db_reader.get_workspace_by_name(workspace_name)
142144
if not selected_workspace:
143145
raise WorkspaceDoesNotExistError(f"Workspace {workspace_name} does not exist.")
144146

145147
custom_instructions = " ".join(custom_instr_lst)
146-
workspace_update = Workspace(
148+
workspace_update = WorkspaceRow(
147149
id=selected_workspace.id,
148150
name=selected_workspace.name,
149151
custom_instructions=custom_instructions,
@@ -195,7 +197,7 @@ async def hard_delete_workspace(self, workspace_name: str):
195197
raise WorkspaceCrudError(f"Error deleting workspace {workspace_name}")
196198
return
197199

198-
async def get_workspace_by_name(self, workspace_name: str) -> Workspace:
200+
async def get_workspace_by_name(self, workspace_name: str) -> WorkspaceRow:
199201
workspace = await self._db_reader.get_workspace_by_name(workspace_name)
200202
if not workspace:
201203
raise WorkspaceDoesNotExistError(f"Workspace {workspace_name} does not exist.")

tests/pipeline/workspace/test_workspace.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
import pytest
44

5-
from codegate.db.models import Workspace as WorkspaceModel
6-
from codegate.db.models import WorkspaceActive
5+
from codegate.db.models import WorkspaceRow as WorkspaceModel
6+
from codegate.db.models import WorkspaceWithSessionInfo
77
from codegate.pipeline.cli.commands import Workspace
88

99

@@ -17,18 +17,18 @@
1717
(
1818
[
1919
# We'll make a MagicMock that simulates a workspace
20-
# with 'name' attribute and 'active_workspace_id' set
21-
WorkspaceActive(id="1", name="Workspace1", active_workspace_id="100")
20+
# with 'name' attribute and 'session_id' set
21+
WorkspaceWithSessionInfo(id="1", name="Workspace1", session_id="100")
2222
],
23-
"- Workspace1 **(active)**\n",
23+
"- workspace1 **(active)**\n",
2424
),
2525
# Case 3: Multiple workspaces, second one active
2626
(
2727
[
28-
WorkspaceActive(id="1", name="Workspace1", active_workspace_id=None),
29-
WorkspaceActive(id="2", name="Workspace2", active_workspace_id="200"),
28+
WorkspaceWithSessionInfo(id="1", name="Workspace1", session_id=None),
29+
WorkspaceWithSessionInfo(id="2", name="Workspace2", session_id="200"),
3030
],
31-
"- Workspace1\n- Workspace2 **(active)**\n",
31+
"- workspace1\n- workspace2 **(active)**\n",
3232
),
3333
],
3434
)

0 commit comments

Comments
 (0)