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

Commit dbf3e80

Browse files
committed
Use exceptions for handling workspace add error
This stops using the boolean and instead will raise exceptions if there's an issue adding a workspace. This will help us differentiate if the operation failed due to a name already being taken, or the name having invalid characters. Signed-off-by: Juan Antonio Osorio <[email protected]>
1 parent b68186c commit dbf3e80

File tree

4 files changed

+46
-32
lines changed

4 files changed

+46
-32
lines changed

src/codegate/api/v1.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from fastapi.routing import APIRoute
44

55
from codegate.api import v1_models
6+
from codegate.db.connection import AlreadyExistsError
67
from codegate.workspaces.crud import WorkspaceCrud
78

89
v1 = APIRouter()
@@ -52,13 +53,13 @@ async def activate_workspace(request: v1_models.ActivateWorkspaceRequest, status
5253
async def create_workspace(request: v1_models.CreateWorkspaceRequest):
5354
"""Create a new workspace."""
5455
# Input validation is done in the model
55-
created = await wscrud.add_workspace(request.name)
56+
try:
57+
created = await wscrud.add_workspace(request.name)
58+
except AlreadyExistsError:
59+
raise HTTPException(status_code=409, detail="Workspace already exists")
5660

57-
# TODO: refactor to use a more specific exception
58-
if not created:
59-
raise HTTPException(status_code=400, detail="Failed to create workspace")
60-
61-
return v1_models.Workspace(name=request.name)
61+
if created:
62+
return v1_models.Workspace(name=created.name)
6263

6364

6465
@v1.delete(

src/codegate/db/connection.py

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@
77
import structlog
88
from alembic import command as alembic_command
99
from alembic.config import Config as AlembicConfig
10-
from pydantic import BaseModel, ValidationError
10+
from pydantic import BaseModel
1111
from sqlalchemy import CursorResult, TextClause, text
1212
from sqlalchemy.exc import OperationalError
1313
from sqlalchemy.ext.asyncio import create_async_engine
14+
from sqlite3 import IntegrityError
1415

1516
from codegate.db.fim_cache import FimCache
1617
from codegate.db.models import (
@@ -30,6 +31,8 @@
3031
alert_queue = asyncio.Queue()
3132
fim_cache = FimCache()
3233

34+
class AlreadyExistsError(Exception):
35+
pass
3336

3437
class DbCodeGate:
3538
_instance = None
@@ -70,11 +73,11 @@ def __init__(self, sqlite_path: Optional[str] = None):
7073
super().__init__(sqlite_path)
7174

7275
async def _execute_update_pydantic_model(
73-
self, model: BaseModel, sql_command: TextClause
76+
self, model: BaseModel, sql_command: TextClause, should_raise: bool = False
7477
) -> Optional[BaseModel]:
7578
"""Execute an update or insert command for a Pydantic model."""
76-
async with self._async_db_engine.begin() as conn:
77-
try:
79+
try:
80+
async with self._async_db_engine.begin() as conn:
7881
result = await conn.execute(sql_command, model.model_dump())
7982
row = result.first()
8083
if row is None:
@@ -83,9 +86,11 @@ async def _execute_update_pydantic_model(
8386
# Get the class of the Pydantic object to create a new object
8487
model_class = model.__class__
8588
return model_class(**row._asdict())
86-
except Exception as e:
87-
logger.error(f"Failed to update model: {model}.", error=str(e))
88-
return None
89+
except Exception as e:
90+
logger.error(f"Failed to update model: {model}.", error=str(e))
91+
if should_raise:
92+
raise e
93+
return None
8994

9095
async def record_request(self, prompt_params: Optional[Prompt] = None) -> Optional[Prompt]:
9196
if prompt_params is None:
@@ -243,11 +248,14 @@ async def record_context(self, context: Optional[PipelineContext]) -> None:
243248
logger.error(f"Failed to record context: {context}.", error=str(e))
244249

245250
async def add_workspace(self, workspace_name: str) -> Optional[Workspace]:
246-
try:
247-
workspace = Workspace(id=str(uuid.uuid4()), name=workspace_name)
248-
except ValidationError as e:
249-
logger.error(f"Failed to create workspace with name: {workspace_name}: {str(e)}")
250-
return None
251+
"""Add a new workspace to the DB.
252+
253+
This handles validation and insertion of a new workspace.
254+
255+
It may raise a ValidationError if the workspace name is invalid.
256+
or a AlreadyExistsError if the workspace already exists.
257+
"""
258+
workspace = Workspace(id=str(uuid.uuid4()), name=workspace_name)
251259

252260
sql = text(
253261
"""
@@ -256,12 +264,12 @@ async def add_workspace(self, workspace_name: str) -> Optional[Workspace]:
256264
RETURNING *
257265
"""
258266
)
267+
259268
try:
260-
added_workspace = await self._execute_update_pydantic_model(workspace, sql)
261-
except Exception as e:
269+
added_workspace = await self._execute_update_pydantic_model(workspace, sql, should_raise=True)
270+
except IntegrityError as e:
262271
logger.error(f"Failed to add workspace: {workspace_name}.", error=str(e))
263-
return None
264-
272+
raise AlreadyExistsError(f"Workspace {workspace_name} already exists.")
265273
return added_workspace
266274

267275
async def update_session(self, session: Session) -> Optional[Session]:

src/codegate/pipeline/cli/commands.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from abc import ABC, abstractmethod
2+
from pydantic import ValidationError
23
from typing import List
34

45
from codegate import __version__
6+
from codegate.db.connection import AlreadyExistsError
57
from codegate.workspaces.crud import WorkspaceCrud
68

79

@@ -69,14 +71,14 @@ async def _add_workspace(self, args: List[str]) -> str:
6971
if not new_workspace_name:
7072
return "Please provide a name. Use `codegate workspace add your_workspace_name`"
7173

72-
workspace_created = await self.workspace_crud.add_workspace(new_workspace_name)
73-
if not workspace_created:
74-
return (
75-
"Something went wrong. Workspace could not be added.\n"
76-
"1. Check if the name is alphanumeric and only contains dashes, and underscores.\n"
77-
"2. Check if the workspace already exists."
78-
)
79-
return f"Workspace **{new_workspace_name}** has been added"
74+
try:
75+
workspace_created = await self.workspace_crud.add_workspace(new_workspace_name)
76+
except ValidationError as e:
77+
return f"Invalid workspace name: {e}"
78+
except AlreadyExistsError as e:
79+
return f"Workspace **{new_workspace_name}** already exists"
80+
81+
return f"Workspace **{workspace_created.name}** has been added"
8082

8183
async def _activate_workspace(self, args: List[str]) -> str:
8284
"""

src/codegate/workspaces/crud.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55
from codegate.db.models import Session, Workspace, WorkspaceActive, ActiveWorkspace
66

77

8+
class WorkspaceCrudError(Exception):
9+
pass
10+
811
class WorkspaceCrud:
912

1013
def __init__(self):
1114
self._db_reader = DbReader()
1215

13-
async def add_workspace(self, new_workspace_name: str) -> bool:
16+
async def add_workspace(self, new_workspace_name: str) -> Workspace:
1417
"""
1518
Add a workspace
1619
@@ -19,7 +22,7 @@ async def add_workspace(self, new_workspace_name: str) -> bool:
1922
"""
2023
db_recorder = DbRecorder()
2124
workspace_created = await db_recorder.add_workspace(new_workspace_name)
22-
return bool(workspace_created)
25+
return workspace_created
2326

2427
async def get_workspaces(self)-> List[WorkspaceActive]:
2528
"""

0 commit comments

Comments
 (0)