Skip to content

Commit 4831f24

Browse files
committed
v1.1.0
1 parent 58aa5db commit 4831f24

5 files changed

Lines changed: 306 additions & 1 deletion

File tree

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,34 @@ import base64
116116
pdf_bytes = base64.b64decode(result["buffer"])
117117
```
118118

119+
### Sheet Generation
120+
121+
Generate CSV, Markdown, or XLSX spreadsheets from structured data.
122+
123+
```python
124+
result = client.generate_sheet(
125+
format="xlsx",
126+
sheets=[
127+
{
128+
"name": "Invoices",
129+
"columns": [
130+
{"name": "Company", "width": 20},
131+
{"name": "Total", "width": 15},
132+
],
133+
"rows": [
134+
[
135+
{"value": "Acme Corp"},
136+
{"value": 1500.50, "format": "currency", "currency_code": "EUR"},
137+
],
138+
],
139+
},
140+
],
141+
)
142+
143+
import base64
144+
sheet_bytes = base64.b64decode(result["buffer"])
145+
```
146+
119147
### Webhooks (Async)
120148

121149
Use the `*_async` methods to receive results via webhook instead of waiting for the response.

iterationlayer/client.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
ImageFontDefinition,
1616
ImageOutputFormat,
1717
Layer,
18+
SheetDefinition,
19+
SheetFontDefinition,
20+
SheetFormat,
21+
SheetStyles,
1822
TransformOperation,
1923
)
2024
from iterationlayer.types import Dimensions as ImageDimensions
@@ -161,6 +165,45 @@ def generate_document_async(
161165

162166
return cast(AsyncResult, self._post("/document-generation/v1/generate", body))
163167

168+
def generate_sheet(
169+
self,
170+
*,
171+
format: SheetFormat,
172+
sheets: list[SheetDefinition],
173+
styles: SheetStyles | None = None,
174+
fonts: list[SheetFontDefinition] | None = None,
175+
) -> BinaryResult:
176+
body: dict[str, Any] = {"format": format, "sheets": sheets}
177+
178+
if styles is not None:
179+
body["styles"] = styles
180+
if fonts is not None:
181+
body["fonts"] = fonts
182+
183+
return cast(BinaryResult, self._post("/sheet-generation/v1/generate", body))
184+
185+
def generate_sheet_async(
186+
self,
187+
*,
188+
format: SheetFormat,
189+
sheets: list[SheetDefinition],
190+
webhook_url: str,
191+
styles: SheetStyles | None = None,
192+
fonts: list[SheetFontDefinition] | None = None,
193+
) -> AsyncResult:
194+
body: dict[str, Any] = {
195+
"format": format,
196+
"sheets": sheets,
197+
"webhook_url": webhook_url,
198+
}
199+
200+
if styles is not None:
201+
body["styles"] = styles
202+
if fonts is not None:
203+
body["fonts"] = fonts
204+
205+
return cast(AsyncResult, self._post("/sheet-generation/v1/generate", body))
206+
164207
def close(self) -> None:
165208
self._client.close()
166209

iterationlayer/types.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,11 @@ class SolidColorLayer(_SolidColorLayerRequired, total=False):
529529
rotation_in_degrees: float
530530
opacity: int
531531
angled_edges: list[AngledEdge]
532+
border_radius: int
533+
border_top_left_radius: int
534+
border_top_right_radius: int
535+
border_bottom_left_radius: int
536+
border_bottom_right_radius: int
532537

533538

534539
class _TextLayerRequired(TypedDict):
@@ -567,6 +572,11 @@ class ImageLayer(_ImageLayerRequired, total=False):
567572
opacity: int
568573
should_use_smart_cropping: bool
569574
should_remove_background: bool
575+
border_radius: int
576+
border_top_left_radius: int
577+
border_top_right_radius: int
578+
border_bottom_left_radius: int
579+
border_bottom_right_radius: int
570580

571581

572582
class _ImageLayerRequired(TypedDict):
@@ -624,6 +634,38 @@ class GradientLayer(_GradientLayerRequired, total=False):
624634
angle_in_degrees: float
625635
rotation_in_degrees: float
626636
opacity: int
637+
border_radius: int
638+
border_top_left_radius: int
639+
border_top_right_radius: int
640+
border_bottom_left_radius: int
641+
border_bottom_right_radius: int
642+
643+
644+
class _LayoutLayerRequired(TypedDict):
645+
type: Literal["layout"]
646+
index: int
647+
layers: list["Layer"]
648+
649+
650+
class LayoutLayer(_LayoutLayerRequired, total=False):
651+
direction: Literal["horizontal", "vertical"]
652+
gap: int
653+
horizontal_alignment: Literal["start", "center", "end"]
654+
vertical_alignment: Literal["start", "center", "end"]
655+
position: Position
656+
dimensions: Dimensions
657+
opacity: int
658+
background_color: str
659+
padding: int
660+
padding_top: int
661+
padding_right: int
662+
padding_bottom: int
663+
padding_left: int
664+
border_radius: int
665+
border_top_left_radius: int
666+
border_top_right_radius: int
667+
border_bottom_left_radius: int
668+
border_bottom_right_radius: int
627669

628670

629671
Layer = Union[
@@ -635,6 +677,7 @@ class GradientLayer(_GradientLayerRequired, total=False):
635677
QrCodeLayer,
636678
BarcodeLayer,
637679
GradientLayer,
680+
LayoutLayer,
638681
]
639682

640683

@@ -1103,3 +1146,93 @@ class _GenerateDocumentRequestRequired(TypedDict):
11031146

11041147
class GenerateDocumentRequest(_GenerateDocumentRequestRequired, total=False):
11051148
webhook_url: str
1149+
1150+
1151+
# ── Sheet Generation ─────────────────────────────────────────────────────
1152+
1153+
SheetFormat = Literal["csv", "markdown", "xlsx"]
1154+
1155+
SheetCellFormat = Literal[
1156+
"text",
1157+
"number",
1158+
"decimal",
1159+
"currency",
1160+
"percentage",
1161+
"date",
1162+
"datetime",
1163+
"time",
1164+
"custom",
1165+
]
1166+
1167+
SheetNumberStyle = Literal[
1168+
"comma_period",
1169+
"period_comma",
1170+
"space_comma",
1171+
"space_period",
1172+
]
1173+
1174+
1175+
class SheetCellStyle(TypedDict, total=False):
1176+
bold: bool
1177+
italic: bool
1178+
font_color: str
1179+
background_color: str
1180+
font_size: int
1181+
horizontal_alignment: Literal["left", "center", "right"]
1182+
1183+
1184+
class _SheetCellRequired(TypedDict):
1185+
value: object
1186+
1187+
1188+
class SheetCell(_SheetCellRequired, total=False):
1189+
format: SheetCellFormat
1190+
currency_code: str
1191+
number_style: SheetNumberStyle
1192+
date_style: str
1193+
styles: SheetCellStyle
1194+
from_col: int
1195+
to_col: int
1196+
from_row: int
1197+
to_row: int
1198+
1199+
1200+
class _SheetColumnRequired(TypedDict):
1201+
name: str
1202+
1203+
1204+
class SheetColumn(_SheetColumnRequired, total=False):
1205+
width: int
1206+
1207+
1208+
class _SheetDefinitionRequired(TypedDict):
1209+
columns: list[SheetColumn]
1210+
1211+
1212+
class SheetDefinition(_SheetDefinitionRequired, total=False):
1213+
name: str
1214+
rows: list[list[SheetCell]]
1215+
1216+
1217+
class SheetFontDefinition(TypedDict):
1218+
name: str
1219+
buffer: str
1220+
1221+
1222+
class SheetStyles(TypedDict, total=False):
1223+
header_font_color: str
1224+
header_background_color: str
1225+
header_font_size: int
1226+
header_bold: bool
1227+
alternate_row_color: str
1228+
1229+
1230+
class _GenerateSheetRequestRequired(TypedDict):
1231+
format: SheetFormat
1232+
sheets: list[SheetDefinition]
1233+
1234+
1235+
class GenerateSheetRequest(_GenerateSheetRequestRequired, total=False):
1236+
styles: SheetStyles
1237+
fonts: list[SheetFontDefinition]
1238+
webhook_url: str

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "iterationlayer"
7-
version = "1.0.0"
7+
version = "1.1.0"
88
description = "Official Python SDK for the Iteration Layer API"
99
readme = "README.md"
1010
license = "MIT"

tests/test_client.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,107 @@ def test_async_result_with_webhook_url(self, client: IterationLayer) -> None:
377377
assert result == {"async": True, "message": "Processing started"}
378378

379379

380+
class TestGenerateSheet:
381+
@respx.mock
382+
def test_sends_correct_request(self, client: IterationLayer) -> None:
383+
route = respx.post(f"{BASE_URL}/sheet-generation/v1/generate").mock(
384+
return_value=httpx.Response(
385+
200,
386+
json={
387+
"success": True,
388+
"data": {
389+
"buffer": "c2hlZXRkYXRh",
390+
"mime_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
391+
},
392+
},
393+
)
394+
)
395+
396+
result = client.generate_sheet(
397+
format="xlsx",
398+
sheets=[
399+
{
400+
"name": "Invoices",
401+
"columns": [
402+
{"name": "Company", "width": 20},
403+
{"name": "Total", "width": 15},
404+
],
405+
"rows": [
406+
[
407+
{"value": "Acme Corp"},
408+
{"value": 1500.50, "format": "currency", "currency_code": "EUR"},
409+
],
410+
],
411+
},
412+
],
413+
)
414+
415+
assert route.called
416+
request_body = route.calls[0].request.content
417+
assert b'"format"' in request_body
418+
assert b'"sheets"' in request_body
419+
assert result["buffer"] == "c2hlZXRkYXRh"
420+
assert (
421+
result["mime_type"]
422+
== "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
423+
)
424+
425+
@respx.mock
426+
def test_includes_optional_params(self, client: IterationLayer) -> None:
427+
route = respx.post(f"{BASE_URL}/sheet-generation/v1/generate").mock(
428+
return_value=httpx.Response(
429+
200,
430+
json={
431+
"success": True,
432+
"data": {
433+
"buffer": "c2hlZXRkYXRh",
434+
"mime_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
435+
},
436+
},
437+
)
438+
)
439+
440+
client.generate_sheet(
441+
format="xlsx",
442+
sheets=[
443+
{
444+
"columns": [{"name": "Name"}],
445+
"rows": [[{"value": "Test"}]],
446+
},
447+
],
448+
styles={"header_bold": True, "header_background_color": "#f0f0f0"},
449+
fonts=[{"name": "CustomFont", "buffer": "Zm9udGRhdGE="}],
450+
)
451+
452+
request_body = route.calls[0].request.content
453+
assert b'"styles"' in request_body
454+
assert b'"fonts"' in request_body
455+
456+
457+
class TestGenerateSheetAsync:
458+
@respx.mock
459+
def test_async_result_with_webhook_url(self, client: IterationLayer) -> None:
460+
respx.post(f"{BASE_URL}/sheet-generation/v1/generate").mock(
461+
return_value=httpx.Response(
462+
202,
463+
json={"success": True, "async": True, "message": "Processing started"},
464+
)
465+
)
466+
467+
result = client.generate_sheet_async(
468+
format="xlsx",
469+
sheets=[
470+
{
471+
"columns": [{"name": "Company"}],
472+
"rows": [[{"value": "Acme Corp"}]],
473+
},
474+
],
475+
webhook_url="https://example.com/webhook",
476+
)
477+
478+
assert result == {"async": True, "message": "Processing started"}
479+
480+
380481
class TestErrorHandling:
381482
@respx.mock
382483
def test_raises_iteration_layer_error_on_error_response(self, client: IterationLayer) -> None:

0 commit comments

Comments
 (0)