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

Commit f310e46

Browse files
feat: initial work on endpoints for creating/updating workspace config (#1107)
* feat: initial work on endpoints for creating/updating * fix: return newly created `FullWorkspace` from POST /api/v1/workspaces * formatting * test: create workspace with config happy path * 1 db per test * test: basic happy path test for create/update workspace config * fix failing test * chore: fmt pass * fix: internal server error when no config passed * tidy up * test: more integration tests * chore: tidy ups * chore: revert openapi changes * lint fixes * remove manual rollbacks, ensure re-raising all exceptions
1 parent 599d8f6 commit f310e46

File tree

7 files changed

+562
-73
lines changed

7 files changed

+562
-73
lines changed

api/openapi.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2030,4 +2030,4 @@
20302030
}
20312031
}
20322032
}
2033-
}
2033+
}

src/codegate/api/v1.py

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -248,22 +248,18 @@ async def activate_workspace(request: v1_models.ActivateWorkspaceRequest, status
248248

249249
@v1.post("/workspaces", tags=["Workspaces"], generate_unique_id_function=uniq_name, status_code=201)
250250
async def create_workspace(
251-
request: v1_models.CreateOrRenameWorkspaceRequest,
252-
) -> v1_models.Workspace:
251+
request: v1_models.FullWorkspace,
252+
) -> v1_models.FullWorkspace:
253253
"""Create a new workspace."""
254-
if request.rename_to is not None:
255-
return await rename_workspace(request)
256-
return await create_new_workspace(request)
257-
258-
259-
async def create_new_workspace(
260-
request: v1_models.CreateOrRenameWorkspaceRequest,
261-
) -> v1_models.Workspace:
262-
# Input validation is done in the model
263254
try:
264-
_ = await wscrud.add_workspace(request.name)
265-
except AlreadyExistsError:
266-
raise HTTPException(status_code=409, detail="Workspace already exists")
255+
custom_instructions = request.config.custom_instructions if request.config else None
256+
muxing_rules = request.config.muxing_rules if request.config else None
257+
258+
workspace_row, mux_rules = await wscrud.add_workspace(
259+
request.name, custom_instructions, muxing_rules
260+
)
261+
except crud.WorkspaceNameAlreadyInUseError:
262+
raise HTTPException(status_code=409, detail="Workspace name already in use")
267263
except ValidationError:
268264
raise HTTPException(
269265
status_code=400,
@@ -277,18 +273,40 @@ async def create_new_workspace(
277273
except Exception:
278274
raise HTTPException(status_code=500, detail="Internal server error")
279275

280-
return v1_models.Workspace(name=request.name, is_active=False)
276+
return v1_models.FullWorkspace(
277+
name=workspace_row.name,
278+
config=v1_models.WorkspaceConfig(
279+
custom_instructions=workspace_row.custom_instructions or "",
280+
muxing_rules=[mux_models.MuxRule.from_db_mux_rule(mux_rule) for mux_rule in mux_rules],
281+
),
282+
)
281283

282284

283-
async def rename_workspace(
284-
request: v1_models.CreateOrRenameWorkspaceRequest,
285-
) -> v1_models.Workspace:
285+
@v1.put(
286+
"/workspaces/{workspace_name}",
287+
tags=["Workspaces"],
288+
generate_unique_id_function=uniq_name,
289+
status_code=201,
290+
)
291+
async def update_workspace(
292+
workspace_name: str,
293+
request: v1_models.FullWorkspace,
294+
) -> v1_models.FullWorkspace:
295+
"""Update a workspace."""
286296
try:
287-
_ = await wscrud.rename_workspace(request.name, request.rename_to)
297+
custom_instructions = request.config.custom_instructions if request.config else None
298+
muxing_rules = request.config.muxing_rules if request.config else None
299+
300+
workspace_row, mux_rules = await wscrud.update_workspace(
301+
workspace_name,
302+
request.name,
303+
custom_instructions,
304+
muxing_rules,
305+
)
288306
except crud.WorkspaceDoesNotExistError:
289307
raise HTTPException(status_code=404, detail="Workspace does not exist")
290-
except AlreadyExistsError:
291-
raise HTTPException(status_code=409, detail="Workspace already exists")
308+
except crud.WorkspaceNameAlreadyInUseError:
309+
raise HTTPException(status_code=409, detail="Workspace name already in use")
292310
except ValidationError:
293311
raise HTTPException(
294312
status_code=400,
@@ -302,7 +320,13 @@ async def rename_workspace(
302320
except Exception:
303321
raise HTTPException(status_code=500, detail="Internal server error")
304322

305-
return v1_models.Workspace(name=request.rename_to, is_active=False)
323+
return v1_models.FullWorkspace(
324+
name=workspace_row.name,
325+
config=v1_models.WorkspaceConfig(
326+
custom_instructions=workspace_row.custom_instructions or "",
327+
muxing_rules=[mux_models.MuxRule.from_db_mux_rule(mux_rule) for mux_rule in mux_rules],
328+
),
329+
)
306330

307331

308332
@v1.delete(

src/codegate/api/v1_models.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def from_db_workspaces(
6161

6262

6363
class WorkspaceConfig(pydantic.BaseModel):
64-
system_prompt: str
64+
custom_instructions: str
6565

6666
muxing_rules: List[mux_models.MuxRule]
6767

@@ -72,13 +72,6 @@ class FullWorkspace(pydantic.BaseModel):
7272
config: Optional[WorkspaceConfig] = None
7373

7474

75-
class CreateOrRenameWorkspaceRequest(FullWorkspace):
76-
# If set, rename the workspace to this name. Note that
77-
# the 'name' field is still required and the workspace
78-
# workspace must exist.
79-
rename_to: Optional[str] = None
80-
81-
8275
class ActivateWorkspaceRequest(pydantic.BaseModel):
8376
name: str
8477

src/codegate/db/connection.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
from sqlalchemy import CursorResult, TextClause, event, text
1515
from sqlalchemy.engine import Engine
1616
from sqlalchemy.exc import IntegrityError, OperationalError
17-
from sqlalchemy.ext.asyncio import create_async_engine
17+
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
18+
from sqlalchemy.orm import sessionmaker
1819

1920
from codegate.db.fim_cache import FimCache
2021
from codegate.db.models import (
@@ -1025,6 +1026,34 @@ async def get_distance_to_persona(
10251026
return persona_distance[0]
10261027

10271028

1029+
class DbTransaction:
1030+
def __init__(self):
1031+
self._session = None
1032+
1033+
async def __aenter__(self):
1034+
self._session = sessionmaker(
1035+
bind=DbCodeGate()._async_db_engine,
1036+
class_=AsyncSession,
1037+
expire_on_commit=False,
1038+
)()
1039+
await self._session.begin()
1040+
return self
1041+
1042+
async def __aexit__(self, exc_type, exc_val, exc_tb):
1043+
if exc_type:
1044+
await self._session.rollback()
1045+
raise exc_val
1046+
else:
1047+
await self._session.commit()
1048+
await self._session.close()
1049+
1050+
async def commit(self):
1051+
await self._session.commit()
1052+
1053+
async def rollback(self):
1054+
await self._session.rollback()
1055+
1056+
10281057
def init_db_sync(db_path: Optional[str] = None):
10291058
"""DB will be initialized in the constructor in case it doesn't exist."""
10301059
current_dir = Path(__file__).parent

src/codegate/pipeline/cli/commands.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ def help(self) -> str:
9898

9999

100100
class CodegateCommandSubcommand(CodegateCommand):
101-
102101
@property
103102
@abstractmethod
104103
def subcommands(self) -> Dict[str, Callable[[List[str]], Awaitable[str]]]:
@@ -174,7 +173,6 @@ async def run(self, args: List[str]) -> str:
174173

175174

176175
class Workspace(CodegateCommandSubcommand):
177-
178176
def __init__(self):
179177
self.workspace_crud = crud.WorkspaceCrud()
180178

@@ -258,7 +256,7 @@ async def _rename_workspace(self, flags: Dict[str, str], args: List[str]) -> str
258256
)
259257

260258
try:
261-
await self.workspace_crud.rename_workspace(old_workspace_name, new_workspace_name)
259+
await self.workspace_crud.update_workspace(old_workspace_name, new_workspace_name)
262260
except crud.WorkspaceDoesNotExistError:
263261
return f"Workspace **{old_workspace_name}** does not exist"
264262
except AlreadyExistsError:
@@ -410,7 +408,6 @@ def help(self) -> str:
410408

411409

412410
class CustomInstructions(CodegateCommandSubcommand):
413-
414411
def __init__(self):
415412
self.workspace_crud = crud.WorkspaceCrud()
416413

0 commit comments

Comments
 (0)