Skip to content
This repository was archived by the owner on Jun 5, 2025. It is now read-only.

Commit 8d8ed02

Browse files
Hard delete workspaces
Closes: #669 To hard delete a workspace it first needs to be soft deleted.
1 parent f11cbdf commit 8d8ed02

File tree

4 files changed

+87
-10
lines changed

4 files changed

+87
-10
lines changed

src/codegate/db/connection.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,19 @@ async def soft_delete_workspace(self, workspace: Workspace) -> Optional[Workspac
318318
)
319319
return deleted_workspace
320320

321+
async def hard_delete_workspace(self, workspace: Workspace) -> Optional[Workspace]:
322+
sql = text(
323+
"""
324+
DELETE FROM workspaces
325+
WHERE id = :id
326+
RETURNING *
327+
"""
328+
)
329+
deleted_workspace = await self._execute_update_pydantic_model(
330+
workspace, sql, should_raise=True
331+
)
332+
return deleted_workspace
333+
321334

322335
class DbReader(DbCodeGate):
323336

@@ -431,7 +444,7 @@ async def get_workspaces(self) -> List[WorkspaceActive]:
431444
workspaces = await self._execute_select_pydantic_model(WorkspaceActive, sql)
432445
return workspaces
433446

434-
async def get_workspace_by_name(self, name: str) -> Optional[Workspace]:
447+
async def get_non_deleted_workspace_by_name(self, name: str) -> Optional[Workspace]:
435448
sql = text(
436449
"""
437450
SELECT
@@ -446,6 +459,21 @@ async def get_workspace_by_name(self, name: str) -> Optional[Workspace]:
446459
)
447460
return workspaces[0] if workspaces else None
448461

462+
async def get_workspace_by_name(self, name: str) -> Optional[Workspace]:
463+
sql = text(
464+
"""
465+
SELECT
466+
id, name, system_prompt, deleted_at
467+
FROM workspaces
468+
WHERE name = :name
469+
"""
470+
)
471+
conditions = {"name": name}
472+
workspaces = await self._exec_select_conditions_to_pydantic(
473+
Workspace, sql, conditions, should_raise=True
474+
)
475+
return workspaces[0] if workspaces else None
476+
449477
async def get_sessions(self) -> List[Session]:
450478
sql = text(
451479
"""

src/codegate/db/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class Workspace(BaseModel):
4444
id: str
4545
name: str
4646
system_prompt: Optional[str]
47+
deleted_at: Optional[datetime.datetime] = None
4748

4849
@field_validator("name", mode="plain")
4950
@classmethod

src/codegate/pipeline/cli/commands.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ def subcommands(self) -> Dict[str, Callable[[List[str]], Awaitable[str]]]:
154154
"add": self._add_workspace,
155155
"activate": self._activate_workspace,
156156
"remove": self._remove_workspace,
157+
"delete": self._delete_workspace,
157158
"rename": self._rename_workspace,
158159
}
159160

@@ -267,6 +268,27 @@ async def _remove_workspace(self, flags: Dict[str, str], args: List[str]) -> str
267268
return "An error occurred while removing the workspace"
268269
return f"Workspace **{workspace_name}** has been removed"
269270

271+
async def _delete_workspace(self, flags: Dict[str, str], args: List[str]) -> str:
272+
"""
273+
Remove a workspace
274+
"""
275+
if args is None or len(args) == 0:
276+
return "Please provide a name. Use `codegate workspace delete workspace_name`"
277+
278+
workspace_name = args[0]
279+
if not workspace_name:
280+
return "Please provide a name. Use `codegate workspace delete workspace_name`"
281+
282+
try:
283+
await self.workspace_crud.hard_delete_workspace(workspace_name)
284+
except crud.WorkspaceDoesNotExistError:
285+
return f"Workspace **{workspace_name}** does not exist"
286+
except crud.WorkspaceCrudError as e:
287+
return str(e)
288+
except Exception:
289+
return "An error occurred while deleting the workspace"
290+
return f"Workspace **{workspace_name}** has been deleted permanently"
291+
270292
@property
271293
def help(self) -> str:
272294
return (
@@ -282,13 +304,17 @@ def help(self) -> str:
282304
"- `activate`: Activate a workspace\n\n"
283305
" - *args*:\n\n"
284306
" - `workspace_name`\n\n"
285-
"- `remove`: Remove a workspace\n\n"
286-
" - *args*:\n\n"
287-
" - `workspace_name`\n\n"
288307
"- `rename`: Rename a workspace\n\n"
289308
" - *args*:\n\n"
290309
" - `workspace_name`\n"
291310
" - `new_workspace_name`\n\n"
311+
"- `remove`: Remove a workspace. It can be recovered later.\n\n"
312+
" - *args*:\n\n"
313+
" - `workspace_name`"
314+
"- `delete`: Delete permanently a workspace and its' associated info. The workspace "
315+
"first needs to be `remove`.\n\n"
316+
" - *args*:\n\n"
317+
" - `workspace_name`"
292318
)
293319

294320

src/codegate/workspaces/crud.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ async def _is_workspace_active(
8989
"""
9090
# TODO: All of this should be done within a transaction.
9191

92-
selected_workspace = await self._db_reader.get_workspace_by_name(workspace_name)
92+
selected_workspace = await self._db_reader.get_non_deleted_workspace_by_name(workspace_name)
9393
if not selected_workspace:
9494
raise WorkspaceDoesNotExistError(f"Workspace {workspace_name} does not exist.")
9595

@@ -118,7 +118,7 @@ async def activate_workspace(self, workspace_name: str):
118118
async def update_workspace_system_prompt(
119119
self, workspace_name: str, sys_prompt_lst: List[str]
120120
) -> Workspace:
121-
selected_workspace = await self._db_reader.get_workspace_by_name(workspace_name)
121+
selected_workspace = await self._db_reader.get_non_deleted_workspace_by_name(workspace_name)
122122
if not selected_workspace:
123123
raise WorkspaceDoesNotExistError(f"Workspace {workspace_name} does not exist.")
124124

@@ -132,7 +132,7 @@ async def update_workspace_system_prompt(
132132
updated_workspace = await db_recorder.update_workspace(workspace_update)
133133
return updated_workspace
134134

135-
async def soft_delete_workspace(self, workspace_name: str):
135+
async def soft_delete_workspace(self, workspace_name: str) -> None:
136136
"""
137137
Soft delete a workspace
138138
"""
@@ -141,11 +141,11 @@ async def soft_delete_workspace(self, workspace_name: str):
141141
if workspace_name == DEFAULT_WORKSPACE_NAME:
142142
raise WorkspaceCrudError("Cannot delete default workspace.")
143143

144-
selected_workspace = await self._db_reader.get_workspace_by_name(workspace_name)
144+
selected_workspace = await self._db_reader.get_non_deleted_workspace_by_name(workspace_name)
145145
if not selected_workspace:
146146
raise WorkspaceDoesNotExistError(f"Workspace {workspace_name} does not exist.")
147147

148-
# Check if workspace is active, if it is, make the default workspace active
148+
# Check if workspace is active, if it is, avoid deleting it
149149
active_workspace = await self._db_reader.get_active_workspace()
150150
if active_workspace and active_workspace.id == selected_workspace.id:
151151
raise WorkspaceCrudError("Cannot delete active workspace.")
@@ -157,8 +157,30 @@ async def soft_delete_workspace(self, workspace_name: str):
157157
raise WorkspaceCrudError(f"Error deleting workspace {workspace_name}")
158158
return
159159

160+
async def hard_delete_workspace(self, workspace_name: str) -> None:
161+
"""
162+
Soft delete a workspace
163+
"""
164+
if workspace_name == "":
165+
raise WorkspaceCrudError("Workspace name cannot be empty.")
166+
167+
selected_workspace = await self._db_reader.get_workspace_by_name(workspace_name)
168+
if not selected_workspace:
169+
raise WorkspaceDoesNotExistError(f"Workspace {workspace_name} does not exist.")
170+
171+
# Check if workspace is soft deleted, if it is not, don't delete it
172+
if not selected_workspace.deleted_at:
173+
raise WorkspaceCrudError("Cannot delete workspace that is not soft-deleted.")
174+
175+
db_recorder = DbRecorder()
176+
try:
177+
_ = await db_recorder.hard_delete_workspace(selected_workspace)
178+
except Exception:
179+
raise WorkspaceCrudError(f"Error deleting workspace {workspace_name}")
180+
return
181+
160182
async def get_workspace_by_name(self, workspace_name: str) -> Workspace:
161-
workspace = await self._db_reader.get_workspace_by_name(workspace_name)
183+
workspace = await self._db_reader.get_non_deleted_workspace_by_name(workspace_name)
162184
if not workspace:
163185
raise WorkspaceDoesNotExistError(f"Workspace {workspace_name} does not exist.")
164186
return workspace

0 commit comments

Comments
 (0)