77import structlog
88from alembic import command as alembic_command
99from alembic .config import Config as AlembicConfig
10- from pydantic import BaseModel , ValidationError
10+ from pydantic import BaseModel
1111from sqlalchemy import CursorResult , TextClause , text
12- from sqlalchemy .exc import OperationalError
12+ from sqlalchemy .exc import IntegrityError , OperationalError
1313from sqlalchemy .ext .asyncio import create_async_engine
1414
1515from codegate .db .fim_cache import FimCache
3030alert_queue = asyncio .Queue ()
3131fim_cache = FimCache ()
3232
33+ class AlreadyExistsError (Exception ):
34+ pass
3335
3436class DbCodeGate :
3537 _instance = None
@@ -70,11 +72,11 @@ def __init__(self, sqlite_path: Optional[str] = None):
7072 super ().__init__ (sqlite_path )
7173
7274 async def _execute_update_pydantic_model (
73- self , model : BaseModel , sql_command : TextClause
75+ self , model : BaseModel , sql_command : TextClause , should_raise : bool = False
7476 ) -> Optional [BaseModel ]:
7577 """Execute an update or insert command for a Pydantic model."""
76- async with self . _async_db_engine . begin () as conn :
77- try :
78+ try :
79+ async with self . _async_db_engine . begin () as conn :
7880 result = await conn .execute (sql_command , model .model_dump ())
7981 row = result .first ()
8082 if row is None :
@@ -83,9 +85,11 @@ async def _execute_update_pydantic_model(
8385 # Get the class of the Pydantic object to create a new object
8486 model_class = model .__class__
8587 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
88+ except Exception as e :
89+ logger .error (f"Failed to update model: { model } ." , error = str (e ))
90+ if should_raise :
91+ raise e
92+ return None
8993
9094 async def record_request (self , prompt_params : Optional [Prompt ] = None ) -> Optional [Prompt ]:
9195 if prompt_params is None :
@@ -243,11 +247,14 @@ async def record_context(self, context: Optional[PipelineContext]) -> None:
243247 logger .error (f"Failed to record context: { context } ." , error = str (e ))
244248
245249 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
250+ """Add a new workspace to the DB.
251+
252+ This handles validation and insertion of a new workspace.
253+
254+ It may raise a ValidationError if the workspace name is invalid.
255+ or a AlreadyExistsError if the workspace already exists.
256+ """
257+ workspace = Workspace (id = str (uuid .uuid4 ()), name = workspace_name )
251258
252259 sql = text (
253260 """
@@ -256,12 +263,13 @@ async def add_workspace(self, workspace_name: str) -> Optional[Workspace]:
256263 RETURNING *
257264 """
258265 )
259- try :
260- added_workspace = await self ._execute_update_pydantic_model (workspace , sql )
261- except Exception as e :
262- logger .error (f"Failed to add workspace: { workspace_name } ." , error = str (e ))
263- return None
264266
267+ try :
268+ added_workspace = await self ._execute_update_pydantic_model (
269+ workspace , sql , should_raise = True )
270+ except IntegrityError as e :
271+ logger .debug (f"Exception type: { type (e )} " )
272+ raise AlreadyExistsError (f"Workspace { workspace_name } already exists." )
265273 return added_workspace
266274
267275 async def update_session (self , session : Session ) -> Optional [Session ]:
0 commit comments