Skip to content

Commit 27fa295

Browse files
committed
v1.0.0
0 parents  commit 27fa295

File tree

9 files changed

+1956
-0
lines changed

9 files changed

+1956
-0
lines changed

.github/workflows/ci.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
check:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
python-version: ["3.9", "3.12", "3.13"]
15+
steps:
16+
- uses: actions/checkout@v4
17+
- uses: actions/setup-python@v5
18+
with:
19+
python-version: ${{ matrix.python-version }}
20+
- run: pip install -e ".[dev]"
21+
- run: ruff check .
22+
- run: ruff format --check .
23+
- run: mypy iterationlayer
24+
- run: pytest

.github/workflows/publish.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: Publish
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
8+
jobs:
9+
publish:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
- uses: actions/setup-python@v5
14+
with:
15+
python-version: "3.13"
16+
- run: pip install build twine
17+
- run: python -m build
18+
- run: twine upload dist/*
19+
env:
20+
TWINE_USERNAME: __token__
21+
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}

README.md

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
# Iteration Layer Python SDK
2+
3+
Official Python SDK for the [Iteration Layer API](https://iterationlayer.com).
4+
5+
## Installation
6+
7+
```sh
8+
pip install iterationlayer
9+
```
10+
11+
## Usage
12+
13+
```python
14+
from iterationlayer import IterationLayer
15+
16+
client = IterationLayer("il_your_api_key")
17+
```
18+
19+
### Document Extraction
20+
21+
Extract structured data from documents using AI.
22+
23+
```python
24+
from iterationlayer import TextFieldConfig, CurrencyAmountFieldConfig
25+
26+
result = client.extract(
27+
files=[{"type": "url", "name": "invoice.pdf", "url": "https://example.com/invoice.pdf"}],
28+
schema={
29+
"fields": [
30+
TextFieldConfig(type="TEXT", name="company_name", description="The company name"),
31+
CurrencyAmountFieldConfig(type="CURRENCY_AMOUNT", name="total", description="The invoice total"),
32+
]
33+
},
34+
)
35+
36+
print(result["company_name"]["value"]) # "Acme Corp"
37+
print(result["company_name"]["confidence"]) # 0.95
38+
```
39+
40+
### Image Transformation
41+
42+
Resize, crop, convert, and apply effects to images.
43+
44+
```python
45+
from iterationlayer import ResizeOperation, ConvertOperation
46+
47+
result = client.transform(
48+
file={"type": "url", "name": "photo.jpg", "url": "https://example.com/photo.jpg"},
49+
operations=[
50+
ResizeOperation(type="resize", width_in_px=800, height_in_px=600, fit="cover"),
51+
ConvertOperation(type="convert", format="webp", quality=85),
52+
],
53+
)
54+
55+
import base64
56+
image_bytes = base64.b64decode(result["buffer"])
57+
```
58+
59+
### Image Generation
60+
61+
Compose images from layer definitions.
62+
63+
```python
64+
from iterationlayer import SolidColorBackgroundLayer, TextLayer
65+
66+
result = client.generate_image(
67+
dimensions={"width_in_px": 1200, "height_in_px": 630},
68+
layers=[
69+
SolidColorBackgroundLayer(type="solid-color-background", index=0, hex_color="#1a1a2e"),
70+
TextLayer(
71+
type="text",
72+
index=1,
73+
text="Hello World",
74+
font_name="Inter",
75+
font_size_in_px=48,
76+
text_color="#ffffff",
77+
position={"x_in_px": 50, "y_in_px": 50},
78+
dimensions={"width_in_px": 1100, "height_in_px": 530},
79+
),
80+
],
81+
output_format="png",
82+
)
83+
84+
import base64
85+
image_bytes = base64.b64decode(result["buffer"])
86+
```
87+
88+
### Document Generation
89+
90+
Generate PDF, DOCX, EPUB, or PPTX from structured data.
91+
92+
```python
93+
from iterationlayer import HeadlineBlock, ParagraphBlock
94+
95+
result = client.generate_document(
96+
format="pdf",
97+
document={
98+
"metadata": {"title": "Invoice #123"},
99+
"page": {
100+
"size": {"preset": "A4"},
101+
"margins": {"top_in_pt": 36, "bottom_in_pt": 36, "left_in_pt": 36, "right_in_pt": 36},
102+
},
103+
"styles": {
104+
"text": {"font_family": "Helvetica", "font_size_in_pt": 12, "line_height": 1.5, "color": "#000000"},
105+
"headline": {"font_family": "Helvetica", "font_size_in_pt": 24, "color": "#000000", "spacing_before_in_pt": 12, "spacing_after_in_pt": 6},
106+
"link": {"color": "#0066cc"},
107+
"list": {"indent_in_pt": 18, "spacing_between_items_in_pt": 4},
108+
"table": {
109+
"header": {"background_color": "#f0f0f0", "font_family": "Helvetica", "font_size_in_pt": 12, "color": "#000000", "padding_in_pt": 6},
110+
"body": {"font_family": "Helvetica", "font_size_in_pt": 12, "color": "#000000", "padding_in_pt": 6},
111+
},
112+
"grid": {"gap_in_pt": 12},
113+
"separator": {"color": "#cccccc", "thickness_in_pt": 1, "margin_top_in_pt": 12, "margin_bottom_in_pt": 12},
114+
"image": {"alignment": "center", "margin_top_in_pt": 8, "margin_bottom_in_pt": 8},
115+
},
116+
"content": [
117+
HeadlineBlock(type="headline", level="h1", text="Invoice #123"),
118+
ParagraphBlock(type="paragraph", markdown="Thank you for your business."),
119+
],
120+
},
121+
)
122+
123+
import base64
124+
pdf_bytes = base64.b64decode(result["buffer"])
125+
```
126+
127+
### Webhooks (Async)
128+
129+
Provide a `webhook_url` to receive results asynchronously.
130+
131+
```python
132+
result = client.extract(
133+
files=[{"type": "url", "name": "invoice.pdf", "url": "https://example.com/invoice.pdf"}],
134+
schema={
135+
"fields": [
136+
CurrencyAmountFieldConfig(type="CURRENCY_AMOUNT", name="total", description="The invoice total"),
137+
]
138+
},
139+
webhook_url="https://your-app.com/webhooks/extraction",
140+
)
141+
142+
# result = {"async": True, "message": "Request accepted..."}
143+
```
144+
145+
### Context Manager
146+
147+
The client can be used as a context manager to ensure the underlying HTTP connection is closed.
148+
149+
```python
150+
with IterationLayer("il_your_api_key") as client:
151+
result = client.extract(...)
152+
```
153+
154+
### Error Handling
155+
156+
```python
157+
from iterationlayer import IterationLayerError
158+
159+
try:
160+
result = client.extract(...)
161+
except IterationLayerError as e:
162+
print(e.status_code) # 422
163+
print(e.error_message) # "Validation error: ..."
164+
```
165+
166+
## Documentation
167+
168+
Full documentation is available at [iterationlayer.com/docs](https://iterationlayer.com/docs).
169+
170+
## Issues & Feedback
171+
172+
Please report bugs and request features in the [issues repository](https://github.com/iterationlayer/issues).

iterationlayer/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from iterationlayer.client import (
2+
IterationLayer,
3+
IterationLayerError,
4+
)
5+
from iterationlayer.types import * # noqa: F401, F403
6+
7+
__all__ = [
8+
"IterationLayer",
9+
"IterationLayerError",
10+
]

iterationlayer/client.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
from __future__ import annotations
2+
3+
from typing import Any, Union, cast
4+
5+
import httpx
6+
7+
from iterationlayer.types import (
8+
AsyncResult,
9+
BinaryResult,
10+
DocumentDefinition,
11+
DocumentFormat,
12+
ExtractionResult,
13+
ExtractionSchema,
14+
FileInput,
15+
ImageFontDefinition,
16+
ImageOutputFormat,
17+
Layer,
18+
TransformOperation,
19+
)
20+
from iterationlayer.types import Dimensions as ImageDimensions
21+
22+
DEFAULT_BASE_URL = "https://api.iterationlayer.com"
23+
DEFAULT_TIMEOUT_IN_S = 300
24+
25+
26+
class IterationLayerError(Exception):
27+
def __init__(self, status_code: int, error_message: str) -> None:
28+
self.status_code = status_code
29+
self.error_message = error_message
30+
super().__init__(f"Iteration Layer API error ({status_code}): {error_message}")
31+
32+
33+
class IterationLayer:
34+
def __init__(
35+
self,
36+
api_key: str,
37+
*,
38+
base_url: str = DEFAULT_BASE_URL,
39+
timeout_in_s: float = DEFAULT_TIMEOUT_IN_S,
40+
) -> None:
41+
self._client = httpx.Client(
42+
base_url=base_url,
43+
headers={
44+
"Authorization": f"Bearer {api_key}",
45+
"Content-Type": "application/json",
46+
},
47+
timeout=timeout_in_s,
48+
)
49+
50+
def extract(
51+
self,
52+
*,
53+
files: list[FileInput],
54+
schema: ExtractionSchema,
55+
webhook_url: str | None = None,
56+
) -> ExtractionResult | AsyncResult:
57+
body: dict[str, Any] = {"files": files, "schema": schema}
58+
59+
if webhook_url is not None:
60+
body["webhook_url"] = webhook_url
61+
62+
return cast(
63+
Union[ExtractionResult, AsyncResult],
64+
self._post("/document-extraction/v1/extract", body),
65+
)
66+
67+
def transform(
68+
self,
69+
*,
70+
file: FileInput,
71+
operations: list[TransformOperation],
72+
webhook_url: str | None = None,
73+
) -> BinaryResult | AsyncResult:
74+
body: dict[str, Any] = {"file": file, "operations": operations}
75+
76+
if webhook_url is not None:
77+
body["webhook_url"] = webhook_url
78+
79+
return cast(
80+
Union[BinaryResult, AsyncResult],
81+
self._post("/image-transformation/v1/transform", body),
82+
)
83+
84+
def generate_image(
85+
self,
86+
*,
87+
dimensions: ImageDimensions,
88+
layers: list[Layer],
89+
fonts: list[ImageFontDefinition] | None = None,
90+
output_format: ImageOutputFormat | None = None,
91+
webhook_url: str | None = None,
92+
) -> BinaryResult | AsyncResult:
93+
body: dict[str, Any] = {"dimensions": dimensions, "layers": layers}
94+
95+
if fonts is not None:
96+
body["fonts"] = fonts
97+
if output_format is not None:
98+
body["output_format"] = output_format
99+
if webhook_url is not None:
100+
body["webhook_url"] = webhook_url
101+
102+
return cast(
103+
Union[BinaryResult, AsyncResult],
104+
self._post("/image-generation/v1/generate", body),
105+
)
106+
107+
def generate_document(
108+
self,
109+
*,
110+
format: DocumentFormat,
111+
document: DocumentDefinition,
112+
webhook_url: str | None = None,
113+
) -> BinaryResult | AsyncResult:
114+
body: dict[str, Any] = {"format": format, "document": document}
115+
116+
if webhook_url is not None:
117+
body["webhook_url"] = webhook_url
118+
119+
return cast(
120+
Union[BinaryResult, AsyncResult],
121+
self._post("/document-generation/v1/generate", body),
122+
)
123+
124+
def close(self) -> None:
125+
self._client.close()
126+
127+
def __enter__(self) -> IterationLayer:
128+
return self
129+
130+
def __exit__(self, *_: Any) -> None:
131+
self.close()
132+
133+
def _post(self, path: str, body: dict[str, Any]) -> Any:
134+
response = self._client.post(path, json=body)
135+
parsed: dict[str, Any] = response.json()
136+
137+
if not parsed.get("success"):
138+
raise IterationLayerError(
139+
response.status_code, str(parsed.get("error", "Unknown error"))
140+
)
141+
142+
if parsed.get("async"):
143+
return {"async": True, "message": parsed["message"]}
144+
145+
return parsed["data"]

0 commit comments

Comments
 (0)