Skip to content

Commit ce88176

Browse files
committed
Update sheets api to work with fe
1 parent f098981 commit ce88176

File tree

4 files changed

+94
-102
lines changed

4 files changed

+94
-102
lines changed

llmstack/sheets/apis.py

Lines changed: 32 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import logging
22
import uuid
33

4-
from asgiref.sync import async_to_sync
5-
from channels.layers import get_channel_layer
64
from rest_framework import status, viewsets
75
from rest_framework.permissions import IsAuthenticated
86
from rest_framework.response import Response as DRFResponse
@@ -14,51 +12,17 @@
1412
PromptlySheetCell,
1513
PromptlySheetRunEntry,
1614
)
17-
from llmstack.sheets.serializers import PromptlySheetSeializer
18-
from llmstack.sheets.utils import parse_formula
15+
from llmstack.sheets.serializers import PromptlySheetSerializer
1916

2017
logger = logging.getLogger(__name__)
2118

2219

23-
def write_to_ws_channel(channel_name, message):
24-
channel_layer = get_channel_layer()
25-
async_to_sync(channel_layer.send)(channel_name, {"type": "send.message", "message": message})
26-
27-
2820
class PromptlySheetAppExecuteJob(ProcessingJob):
2921
@classmethod
3022
def generate_job_id(cls):
3123
return "{}".format(str(uuid.uuid4()))
3224

3325

34-
def _execute_cell(cell: PromptlySheetCell, sheet: PromptlySheet) -> PromptlySheetCell:
35-
if not cell.is_formula:
36-
return cell
37-
38-
# Else execute the formula
39-
function_name, params = parse_formula(cell.value)
40-
if not function_name or not params:
41-
display_value = f"Invalid formula: {cell.value}"
42-
else:
43-
resolved_params = [""] * len(params)
44-
for param_index in range(len(params)):
45-
if "-" in params[param_index]:
46-
column, row = params[param_index].split("-")
47-
resolved_cell = sheet.get_cell(int(row), int(column))
48-
resolved_params[param_index] = resolved_cell.value
49-
50-
display_value = f"Executing {function_name}({', '.join(resolved_params)})"
51-
52-
extra_data = {**cell.extra_data}
53-
extra_data["display_value"] = display_value
54-
new_cell = cell.model_copy(update={"extra_data": extra_data})
55-
ws_channel_name = sheet.extra_data.get("channel_name")
56-
if ws_channel_name:
57-
write_to_ws_channel(ws_channel_name, new_cell.model_dump())
58-
59-
return new_cell
60-
61-
6226
class PromptlySheetViewSet(viewsets.ViewSet):
6327
permission_classes = [IsAuthenticated]
6428

@@ -69,7 +33,7 @@ def list(self, request, sheet_uuid=None):
6933
if sheet_uuid:
7034
sheet = PromptlySheet.objects.get(uuid=sheet_uuid, profile_uuid=profile.uuid)
7135
return DRFResponse(
72-
PromptlySheetSeializer(
36+
PromptlySheetSerializer(
7337
instance=sheet,
7438
context={
7539
"include_cells": include_cells,
@@ -78,7 +42,7 @@ def list(self, request, sheet_uuid=None):
7842
)
7943
sheets = PromptlySheet.objects.filter(profile_uuid=profile.uuid)
8044
return DRFResponse(
81-
PromptlySheetSeializer(
45+
PromptlySheetSerializer(
8246
instance=sheets,
8347
many=True,
8448
context={
@@ -93,6 +57,7 @@ def create(self, request):
9357
sheet = PromptlySheet.objects.create(
9458
name=request.data.get("name"),
9559
profile_uuid=profile.uuid,
60+
data=request.data.get("data", {}),
9661
extra_data=request.data.get("extra_data", {"has_header": True}),
9762
)
9863

@@ -114,7 +79,7 @@ def create(self, request):
11479

11580
sheet.save(cells=cells_data)
11681

117-
return DRFResponse(PromptlySheetSeializer(instance=sheet).data)
82+
return DRFResponse(PromptlySheetSerializer(instance=sheet).data)
11883

11984
def delete(self, request, sheet_uuid=None):
12085
profile = Profile.objects.get(user=request.user)
@@ -130,58 +95,50 @@ def patch(self, request, sheet_uuid=None):
13095
sheet = PromptlySheet.objects.get(uuid=sheet_uuid, profile_uuid=profile.uuid)
13196
sheet.name = request.data.get("name", sheet.name)
13297
sheet.extra_data = request.data.get("extra_data", sheet.extra_data)
98+
99+
if "data" in request.data:
100+
if "columns" in request.data["data"]:
101+
sheet.data["columns"] = request.data["data"]["columns"]
102+
103+
if "total_rows" in request.data["data"]:
104+
sheet.data["total_rows"] = request.data["data"]["total_rows"]
105+
106+
if "description" in request.data["data"]:
107+
sheet.data["description"] = request.data["data"]["description"]
108+
133109
sheet.save()
134110

135-
if "cells" in request.data:
111+
if "data" in request.data and "cells" in request.data["data"]:
136112
cells_data = []
137-
for row in range(len(request.data["cells"])):
138-
cells_row_data = []
139-
for column in range(len(request.data["cells"][row])):
140-
cell_data = request.data["cells"][row][column]
141-
cells_row_data.append(PromptlySheetCell(row=row, col=column, **cell_data))
142-
cells_data.append(cells_row_data)
113+
for row_number in request.data["data"]["cells"]:
114+
if not isinstance(request.data["data"]["cells"][row_number], dict):
115+
raise ValueError("Invalid cells data")
116+
for column_number in request.data["data"]["cells"][row_number]:
117+
cell_data = request.data["data"]["cells"][row_number][column_number]
118+
cell_data.pop("row", None)
119+
cell_data.pop("col", None)
120+
cells_data.append(PromptlySheetCell(row=row_number, col=column_number, **cell_data))
143121
sheet.save(cells=cells_data)
144122

145-
return DRFResponse(PromptlySheetSeializer(instance=sheet).data)
123+
return DRFResponse(PromptlySheetSerializer(instance=sheet).data)
146124

147-
def execute(self, request, sheet_uuid=None):
125+
def run_async(self, request, sheet_uuid=None):
148126
profile = Profile.objects.get(user=request.user)
149-
150127
sheet = PromptlySheet.objects.get(uuid=sheet_uuid, profile_uuid=profile.uuid)
151128
if sheet.is_locked:
152129
return DRFResponse(
153-
{"detail": "The sheet is locked and cannot be executed"},
130+
{"detail": "The sheet is locked and cannot be run at this time."},
154131
status=status.HTTP_400_BAD_REQUEST,
155132
)
156133

157134
sheet.is_locked = True
158135
sheet.save(update_fields=["is_locked"])
159136

160-
try:
161-
processed_cells = {}
162-
for row_number, column_cells in sheet.rows:
163-
processed_cells_row = {}
164-
for column_number in column_cells:
165-
processed_cells_row[column_number] = _execute_cell(column_cells[column_number], sheet)
166-
processed_cells[row_number] = processed_cells_row
167-
168-
if processed_cells:
169-
sheet.save(cells=processed_cells, update_fields=["updated_at"])
170-
# Store the processed data in sheet runs table
171-
run_entry = PromptlySheetRunEntry(sheet_uuid=sheet.uuid, profile_uuid=profile.uuid)
172-
run_entry.save(cells=processed_cells)
173-
174-
except Exception:
175-
logger.exception("Error executing sheet")
176-
177-
sheet.is_locked = False
178-
sheet.save(update_fields=["is_locked"])
179-
return DRFResponse(PromptlySheetSeializer(instance=sheet).data)
137+
run_entry = PromptlySheetRunEntry(sheet_uuid=sheet.uuid, profile_uuid=profile.uuid)
180138

181-
def execute_async(self, request, sheet_uuid=None):
182139
job = PromptlySheetAppExecuteJob.create(
183-
func="llmstack.promptly_sheets.tasks.process_sheet_execute_request",
184-
args=[request.user.email, sheet_uuid],
140+
func="llmstack.sheets.tasks.run_sheet",
141+
args=[sheet, run_entry],
185142
).add_to_queue()
186143

187-
return DRFResponse({"job_id": job.id}, status=202)
144+
return DRFResponse({"job_id": job.id, "run_id": run_entry.uuid}, status=202)

llmstack/sheets/models.py

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import base64
22
import json
3+
import logging
34
import uuid
4-
from typing import Dict
5+
from typing import List
56

67
from django.db import models
78
from django.db.models.signals import post_delete
@@ -10,17 +11,19 @@
1011

1112
from llmstack.assets.models import Assets
1213

14+
logger = logging.getLogger(__name__)
15+
1316

1417
class PromptlySheetCell(BaseModel):
1518
row: int
1619
col: int
17-
value: str = ""
18-
value_type: str = "string"
19-
extra_data: dict = {}
20+
data: str = ""
21+
formula: str = ""
22+
kind: str = "string"
2023

2124
@property
2225
def is_formula(self):
23-
return self.value.startswith("=")
26+
return bool(self.formula)
2427

2528
@property
2629
def cell_id(self):
@@ -94,13 +97,17 @@ def delete_sheet_data_objrefs(data_objrefs):
9497
pass
9598

9699

97-
def create_sheet_data_objrefs(
98-
cells: Dict[int, Dict[int, PromptlySheetCell]], sheet_name, sheet_uuid, page_size: int = 1000
99-
):
100-
for i in range(0, len(cells), page_size):
100+
def create_sheet_data_objrefs(cells: List[PromptlySheetCell], sheet_name, sheet_uuid, page_size: int = 1000):
101+
# Sort the cells by row and columns
102+
cells = sorted(cells, key=lambda cell: (cell.row, cell.col))
103+
max_rows = max(cells, key=lambda cell: cell.row).row + 1
104+
105+
for i in range(0, max_rows, page_size):
101106
chunk = {}
102-
for j in range(i, min(i + page_size, len(cells))):
103-
chunk[j] = dict(map(lambda entry: (entry[0], entry[1].model_dump()), cells[j].items()))
107+
for j in range(i, min(i + page_size, max_rows)):
108+
# Find cells from this row and add them to the chunk
109+
cells_in_row = list(filter(lambda cell: cell.row == j, cells))
110+
chunk[j] = dict(map(lambda cell: (cell.col, cell.model_dump()), cells_in_row))
104111

105112
data_json = json.dumps(chunk)
106113
filename = f"{sheet_name}_{str(uuid.uuid4())[:4]}_{i}.json"
@@ -135,9 +142,12 @@ def get_sheet_cells(objref):
135142
)
136143
)
137144

138-
except Exception:
145+
except Exception as e:
146+
logger.error(f"Error loading sheet data from objref: {e}")
139147
pass
140148

149+
return {}
150+
141151

142152
class PromptlySheet(models.Model):
143153
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
@@ -178,19 +188,22 @@ def rows(self):
178188
for cell_row in cells:
179189
yield (cell_row, cells[cell_row])
180190

191+
@property
192+
def columns(self):
193+
return self.data.get("columns", [])
194+
181195
def save(self, *args, **kwargs):
182-
if kwargs.get("cells"):
196+
if "cells" in kwargs:
183197
if self.data and self.data.get("cells"):
184198
# Delete the older objrefs
185199
delete_sheet_data_objrefs(self.data.get("cells", []))
186200
cell_objs = kwargs.pop("cells")
187-
self.data = {
188-
"cells": list(
189-
create_sheet_data_objrefs(
190-
cell_objs, self.name, str(self.uuid), page_size=self.extra_data.get("page_size", 1000)
191-
)
201+
202+
self.data["cells"] = list(
203+
create_sheet_data_objrefs(
204+
cell_objs, self.name, str(self.uuid), page_size=self.extra_data.get("page_size", 1000)
192205
)
193-
}
206+
)
194207
if kwargs.get("update_fields"):
195208
kwargs["update_fields"].append("data")
196209

llmstack/sheets/serializers.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1+
import logging
2+
13
from rest_framework import serializers
24

35
from llmstack.sheets.models import PromptlySheet
46

7+
logger = logging.getLogger(__name__)
8+
59

6-
class PromptlySheetSeializer(serializers.ModelSerializer):
10+
class PromptlySheetSerializer(serializers.ModelSerializer):
711
cells = serializers.SerializerMethodField()
12+
columns = serializers.SerializerMethodField()
13+
description = serializers.SerializerMethodField()
14+
total_rows = serializers.SerializerMethodField()
815

916
def get_cells(self, obj):
1017
cells = {}
@@ -15,6 +22,25 @@ def get_cells(self, obj):
1522

1623
return cells
1724

25+
def get_columns(self, obj):
26+
return obj.data.get("columns", [])
27+
28+
def get_description(self, obj):
29+
return obj.data.get("description", "")
30+
31+
def get_total_rows(self, obj):
32+
return obj.data.get("total_rows", 0)
33+
1834
class Meta:
1935
model = PromptlySheet
20-
fields = ["uuid", "name", "extra_data", "cells", "created_at", "updated_at"]
36+
fields = [
37+
"uuid",
38+
"name",
39+
"extra_data",
40+
"cells",
41+
"columns",
42+
"total_rows",
43+
"description",
44+
"created_at",
45+
"updated_at",
46+
]

llmstack/sheets/urls.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,7 @@
99
PromptlySheetViewSet.as_view({"get": "list", "patch": "patch", "delete": "delete"}),
1010
),
1111
path(
12-
"api/sheets/<str:sheet_uuid>/execute",
13-
PromptlySheetViewSet.as_view({"post": "execute"}),
14-
),
15-
path(
16-
"api/sheets/<str:sheet_uuid>/execute_async",
17-
PromptlySheetViewSet.as_view({"post": "execute_async"}),
12+
"api/sheets/<str:sheet_uuid>/run",
13+
PromptlySheetViewSet.as_view({"post": "run_async"}),
1814
),
1915
]

0 commit comments

Comments
 (0)