bannerify 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,273 @@
1
+ Metadata-Version: 2.3
2
+ Name: bannerify
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for Bannerify API - Generate images and PDFs at scale
5
+ Keywords: bannerify,image generation,pdf generation,api
6
+ Author: Duc An
7
+ Author-email: Duc An <an@bannerify.co>
8
+ License: MIT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Requires-Dist: httpx>=0.27.0
19
+ Requires-Dist: pydantic>=2.0
20
+ Requires-Dist: python-dotenv>=1.0.1
21
+ Requires-Python: >=3.8
22
+ Project-URL: Documentation, https://bannerify.co/docs
23
+ Project-URL: Homepage, https://bannerify.co
24
+ Project-URL: Issues, https://github.com/bannerify/bannerify-python/issues
25
+ Project-URL: Repository, https://github.com/bannerify/bannerify-python
26
+ Description-Content-Type: text/markdown
27
+
28
+ # Bannerify Python SDK
29
+
30
+ Official Python SDK for [Bannerify](https://bannerify.co) - Generate images and PDFs at scale via API.
31
+
32
+ [![PyPI version](https://badge.fury.io/py/bannerify.svg)](https://badge.fury.io/py/bannerify)
33
+ [![Python versions](https://img.shields.io/pypi/pyversions/bannerify.svg)](https://pypi.org/project/bannerify/)
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ pip install bannerify
39
+ ```
40
+
41
+ Or with uv:
42
+
43
+ ```bash
44
+ uv add bannerify
45
+ ```
46
+
47
+ ## Quick Start
48
+
49
+ ```python
50
+ from bannerify import BannerifyClient
51
+
52
+ # Create client with your API key
53
+ client = BannerifyClient("your-api-key")
54
+
55
+ # Generate an image
56
+ result = client.create_image(
57
+ "tpl_xxxxxxxxx",
58
+ modifications=[
59
+ {"name": "title", "text": "Hello World"},
60
+ {"name": "subtitle", "text": "From Python SDK"}
61
+ ]
62
+ )
63
+
64
+ if "result" in result:
65
+ with open("output.png", "wb") as f:
66
+ f.write(result["result"])
67
+ print("Image created successfully!")
68
+ else:
69
+ print(f"Error: {result['error']['message']}")
70
+ ```
71
+
72
+ ## Features
73
+
74
+ - 🚀 Simple, intuitive API
75
+ - 🔒 Type-safe with full type hints
76
+ - ⚡ Built on httpx for performance
77
+ - 🎯 Result/Error pattern for explicit error handling
78
+ - 📝 Comprehensive documentation
79
+ - ✅ Well-tested
80
+
81
+ ## Usage
82
+
83
+ ### Creating Images
84
+
85
+ ```python
86
+ # Generate PNG
87
+ result = client.create_image(
88
+ "tpl_xxxxxxxxx",
89
+ modifications=[
90
+ {"name": "title", "text": "My Title"},
91
+ {"name": "image", "src": "https://example.com/image.jpg"}
92
+ ]
93
+ )
94
+
95
+ # Generate SVG
96
+ result = client.create_image(
97
+ "tpl_xxxxxxxxx",
98
+ format="svg",
99
+ modifications=[{"name": "title", "text": "My Title"}]
100
+ )
101
+
102
+ # Generate thumbnail
103
+ result = client.create_image("tpl_xxxxxxxxx", thumbnail=True)
104
+ ```
105
+
106
+ ### Creating PDFs
107
+
108
+ ```python
109
+ result = client.create_pdf(
110
+ "tpl_xxxxxxxxx",
111
+ modifications=[{"name": "title", "text": "Invoice #123"}]
112
+ )
113
+
114
+ if "result" in result:
115
+ with open("invoice.pdf", "wb") as f:
116
+ f.write(result["result"])
117
+ ```
118
+
119
+ ### Generating Signed URLs
120
+
121
+ ```python
122
+ signed_url = client.generate_image_signed_url(
123
+ "tpl_xxxxxxxxx",
124
+ modifications=[{"name": "title", "text": "Dynamic Title"}],
125
+ format="png"
126
+ )
127
+
128
+ # Use in HTML
129
+ print(f"<img src='{signed_url}' alt='Generated Image' />")
130
+ ```
131
+
132
+ ### Error Handling
133
+
134
+ ```python
135
+ result = client.create_image("tpl_xxxxxxxxx")
136
+
137
+ if "error" in result:
138
+ error = result["error"]
139
+ print(f"Error Code: {error['code']}")
140
+ print(f"Message: {error['message']}")
141
+ print(f"Docs: {error['docs']}")
142
+ else:
143
+ # Process result
144
+ image_data = result["result"]
145
+ ```
146
+
147
+ ### Context Manager
148
+
149
+ ```python
150
+ with BannerifyClient("your-api-key") as client:
151
+ result = client.create_image("tpl_xxxxxxxxx")
152
+ # Client is automatically closed
153
+ ```
154
+
155
+ ## API Reference
156
+
157
+ ### BannerifyClient
158
+
159
+ ```python
160
+ BannerifyClient(
161
+ api_key: str,
162
+ base_url: str = "https://api.bannerify.co/v1",
163
+ timeout: float = 60.0
164
+ )
165
+ ```
166
+
167
+ ### create_image
168
+
169
+ ```python
170
+ client.create_image(
171
+ template_id: str,
172
+ modifications: Optional[List[Dict[str, Any]]] = None,
173
+ format: str = "png",
174
+ thumbnail: bool = False
175
+ ) -> Dict[str, Any]
176
+ ```
177
+
178
+ ### create_pdf
179
+
180
+ ```python
181
+ client.create_pdf(
182
+ template_id: str,
183
+ modifications: Optional[List[Dict[str, Any]]] = None
184
+ ) -> Dict[str, Any]
185
+ ```
186
+
187
+ ### create_stored_image
188
+
189
+ ```python
190
+ client.create_stored_image(
191
+ template_id: str,
192
+ modifications: Optional[List[Dict[str, Any]]] = None,
193
+ format: str = "png",
194
+ thumbnail: bool = False
195
+ ) -> Dict[str, Any]
196
+ ```
197
+
198
+ ### generate_image_signed_url
199
+
200
+ ```python
201
+ client.generate_image_signed_url(
202
+ template_id: str,
203
+ modifications: Optional[List[Dict[str, Any]]] = None,
204
+ format: str = "png",
205
+ thumbnail: bool = False,
206
+ nocache: bool = False
207
+ ) -> str
208
+ ```
209
+
210
+ ## Examples
211
+
212
+ ### Generate Multiple Images
213
+
214
+ ```python
215
+ products = [
216
+ {"name": "Product A", "price": "$29.99"},
217
+ {"name": "Product B", "price": "$39.99"},
218
+ ]
219
+
220
+ for i, product in enumerate(products):
221
+ result = client.create_image(
222
+ "tpl_product_banner",
223
+ modifications=[
224
+ {"name": "product_name", "text": product["name"]},
225
+ {"name": "price", "text": product["price"]}
226
+ ]
227
+ )
228
+
229
+ if "result" in result:
230
+ with open(f"banner_{i}.png", "wb") as f:
231
+ f.write(result["result"])
232
+ ```
233
+
234
+ ### Email Campaigns
235
+
236
+ ```python
237
+ for recipient in recipients:
238
+ signed_url = client.generate_image_signed_url(
239
+ "tpl_email_header",
240
+ modifications=[{"name": "name", "text": f"Hi, {recipient['name']}!"}]
241
+ )
242
+ # Use signed_url in email HTML
243
+ ```
244
+
245
+ ## Development
246
+
247
+ ```bash
248
+ # Install dependencies
249
+ uv sync
250
+
251
+ # Run tests
252
+ uv run pytest
253
+
254
+ # Type checking
255
+ uv run mypy src/
256
+
257
+ # Format code
258
+ uv run ruff format src/
259
+ ```
260
+
261
+ ## Documentation
262
+
263
+ Full documentation available at [https://bannerify.co/docs/sdk/python/overview](https://bannerify.co/docs/sdk/python/overview)
264
+
265
+ ## Support
266
+
267
+ - Documentation: https://bannerify.co/docs
268
+ - Issues: https://github.com/bannerify/bannerify-python/issues
269
+ - Email: support@bannerify.co
270
+
271
+ ## License
272
+
273
+ MIT License - see LICENSE file for details
@@ -0,0 +1,246 @@
1
+ # Bannerify Python SDK
2
+
3
+ Official Python SDK for [Bannerify](https://bannerify.co) - Generate images and PDFs at scale via API.
4
+
5
+ [![PyPI version](https://badge.fury.io/py/bannerify.svg)](https://badge.fury.io/py/bannerify)
6
+ [![Python versions](https://img.shields.io/pypi/pyversions/bannerify.svg)](https://pypi.org/project/bannerify/)
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ pip install bannerify
12
+ ```
13
+
14
+ Or with uv:
15
+
16
+ ```bash
17
+ uv add bannerify
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ```python
23
+ from bannerify import BannerifyClient
24
+
25
+ # Create client with your API key
26
+ client = BannerifyClient("your-api-key")
27
+
28
+ # Generate an image
29
+ result = client.create_image(
30
+ "tpl_xxxxxxxxx",
31
+ modifications=[
32
+ {"name": "title", "text": "Hello World"},
33
+ {"name": "subtitle", "text": "From Python SDK"}
34
+ ]
35
+ )
36
+
37
+ if "result" in result:
38
+ with open("output.png", "wb") as f:
39
+ f.write(result["result"])
40
+ print("Image created successfully!")
41
+ else:
42
+ print(f"Error: {result['error']['message']}")
43
+ ```
44
+
45
+ ## Features
46
+
47
+ - 🚀 Simple, intuitive API
48
+ - 🔒 Type-safe with full type hints
49
+ - ⚡ Built on httpx for performance
50
+ - 🎯 Result/Error pattern for explicit error handling
51
+ - 📝 Comprehensive documentation
52
+ - ✅ Well-tested
53
+
54
+ ## Usage
55
+
56
+ ### Creating Images
57
+
58
+ ```python
59
+ # Generate PNG
60
+ result = client.create_image(
61
+ "tpl_xxxxxxxxx",
62
+ modifications=[
63
+ {"name": "title", "text": "My Title"},
64
+ {"name": "image", "src": "https://example.com/image.jpg"}
65
+ ]
66
+ )
67
+
68
+ # Generate SVG
69
+ result = client.create_image(
70
+ "tpl_xxxxxxxxx",
71
+ format="svg",
72
+ modifications=[{"name": "title", "text": "My Title"}]
73
+ )
74
+
75
+ # Generate thumbnail
76
+ result = client.create_image("tpl_xxxxxxxxx", thumbnail=True)
77
+ ```
78
+
79
+ ### Creating PDFs
80
+
81
+ ```python
82
+ result = client.create_pdf(
83
+ "tpl_xxxxxxxxx",
84
+ modifications=[{"name": "title", "text": "Invoice #123"}]
85
+ )
86
+
87
+ if "result" in result:
88
+ with open("invoice.pdf", "wb") as f:
89
+ f.write(result["result"])
90
+ ```
91
+
92
+ ### Generating Signed URLs
93
+
94
+ ```python
95
+ signed_url = client.generate_image_signed_url(
96
+ "tpl_xxxxxxxxx",
97
+ modifications=[{"name": "title", "text": "Dynamic Title"}],
98
+ format="png"
99
+ )
100
+
101
+ # Use in HTML
102
+ print(f"<img src='{signed_url}' alt='Generated Image' />")
103
+ ```
104
+
105
+ ### Error Handling
106
+
107
+ ```python
108
+ result = client.create_image("tpl_xxxxxxxxx")
109
+
110
+ if "error" in result:
111
+ error = result["error"]
112
+ print(f"Error Code: {error['code']}")
113
+ print(f"Message: {error['message']}")
114
+ print(f"Docs: {error['docs']}")
115
+ else:
116
+ # Process result
117
+ image_data = result["result"]
118
+ ```
119
+
120
+ ### Context Manager
121
+
122
+ ```python
123
+ with BannerifyClient("your-api-key") as client:
124
+ result = client.create_image("tpl_xxxxxxxxx")
125
+ # Client is automatically closed
126
+ ```
127
+
128
+ ## API Reference
129
+
130
+ ### BannerifyClient
131
+
132
+ ```python
133
+ BannerifyClient(
134
+ api_key: str,
135
+ base_url: str = "https://api.bannerify.co/v1",
136
+ timeout: float = 60.0
137
+ )
138
+ ```
139
+
140
+ ### create_image
141
+
142
+ ```python
143
+ client.create_image(
144
+ template_id: str,
145
+ modifications: Optional[List[Dict[str, Any]]] = None,
146
+ format: str = "png",
147
+ thumbnail: bool = False
148
+ ) -> Dict[str, Any]
149
+ ```
150
+
151
+ ### create_pdf
152
+
153
+ ```python
154
+ client.create_pdf(
155
+ template_id: str,
156
+ modifications: Optional[List[Dict[str, Any]]] = None
157
+ ) -> Dict[str, Any]
158
+ ```
159
+
160
+ ### create_stored_image
161
+
162
+ ```python
163
+ client.create_stored_image(
164
+ template_id: str,
165
+ modifications: Optional[List[Dict[str, Any]]] = None,
166
+ format: str = "png",
167
+ thumbnail: bool = False
168
+ ) -> Dict[str, Any]
169
+ ```
170
+
171
+ ### generate_image_signed_url
172
+
173
+ ```python
174
+ client.generate_image_signed_url(
175
+ template_id: str,
176
+ modifications: Optional[List[Dict[str, Any]]] = None,
177
+ format: str = "png",
178
+ thumbnail: bool = False,
179
+ nocache: bool = False
180
+ ) -> str
181
+ ```
182
+
183
+ ## Examples
184
+
185
+ ### Generate Multiple Images
186
+
187
+ ```python
188
+ products = [
189
+ {"name": "Product A", "price": "$29.99"},
190
+ {"name": "Product B", "price": "$39.99"},
191
+ ]
192
+
193
+ for i, product in enumerate(products):
194
+ result = client.create_image(
195
+ "tpl_product_banner",
196
+ modifications=[
197
+ {"name": "product_name", "text": product["name"]},
198
+ {"name": "price", "text": product["price"]}
199
+ ]
200
+ )
201
+
202
+ if "result" in result:
203
+ with open(f"banner_{i}.png", "wb") as f:
204
+ f.write(result["result"])
205
+ ```
206
+
207
+ ### Email Campaigns
208
+
209
+ ```python
210
+ for recipient in recipients:
211
+ signed_url = client.generate_image_signed_url(
212
+ "tpl_email_header",
213
+ modifications=[{"name": "name", "text": f"Hi, {recipient['name']}!"}]
214
+ )
215
+ # Use signed_url in email HTML
216
+ ```
217
+
218
+ ## Development
219
+
220
+ ```bash
221
+ # Install dependencies
222
+ uv sync
223
+
224
+ # Run tests
225
+ uv run pytest
226
+
227
+ # Type checking
228
+ uv run mypy src/
229
+
230
+ # Format code
231
+ uv run ruff format src/
232
+ ```
233
+
234
+ ## Documentation
235
+
236
+ Full documentation available at [https://bannerify.co/docs/sdk/python/overview](https://bannerify.co/docs/sdk/python/overview)
237
+
238
+ ## Support
239
+
240
+ - Documentation: https://bannerify.co/docs
241
+ - Issues: https://github.com/bannerify/bannerify-python/issues
242
+ - Email: support@bannerify.co
243
+
244
+ ## License
245
+
246
+ MIT License - see LICENSE file for details
@@ -0,0 +1,34 @@
1
+ [project]
2
+ name = "bannerify"
3
+ version = "0.1.0"
4
+ description = "Official Python SDK for Bannerify API - Generate images and PDFs at scale"
5
+ readme = "README.md"
6
+ authors = [{ name = "Duc An", email = "an@bannerify.co" }]
7
+ requires-python = ">=3.8"
8
+ dependencies = ["httpx>=0.27.0", "pydantic>=2.0", "python-dotenv>=1.0.1"]
9
+ license = { text = "MIT" }
10
+ keywords = ["bannerify", "image generation", "pdf generation", "api"]
11
+ classifiers = [
12
+ "Development Status :: 4 - Beta",
13
+ "Intended Audience :: Developers",
14
+ "License :: OSI Approved :: MIT License",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.8",
17
+ "Programming Language :: Python :: 3.9",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ ]
22
+
23
+ [project.urls]
24
+ Homepage = "https://bannerify.co"
25
+ Documentation = "https://bannerify.co/docs"
26
+ Repository = "https://github.com/bannerify/bannerify-python"
27
+ Issues = "https://github.com/bannerify/bannerify-python/issues"
28
+
29
+ [build-system]
30
+ requires = ["uv_build>=0.8.23,<0.9.0"]
31
+ build-backend = "uv_build"
32
+
33
+ [dependency-groups]
34
+ dev = ["datamodel-code-generator>=0.27.3"]
@@ -0,0 +1,7 @@
1
+ """Bannerify Python SDK - Generate images and PDFs at scale"""
2
+
3
+ from .client import BannerifyClient
4
+ from .models import Modification
5
+
6
+ __version__ = "0.1.0"
7
+ __all__ = ["BannerifyClient", "Modification"]
@@ -0,0 +1,338 @@
1
+ """Bannerify Client for Python"""
2
+
3
+ import hashlib
4
+ import json
5
+ from typing import Any, Dict, List, Optional, Union
6
+ from urllib.parse import urlencode
7
+
8
+ import httpx
9
+ from pydantic import TypeAdapter
10
+
11
+ from .models import Modification
12
+
13
+
14
+ class BannerifyClient:
15
+ """Bannerify API Client
16
+
17
+ A simple, developer-friendly client for the Bannerify API.
18
+ Generate images and PDFs from templates at scale.
19
+
20
+ Example:
21
+ >>> client = BannerifyClient("your-api-key")
22
+ >>> result = client.create_image("tpl_xxx", modifications=[
23
+ ... {"name": "title", "text": "Hello World"}
24
+ ... ])
25
+ >>> if "result" in result:
26
+ ... with open("output.png", "wb") as f:
27
+ ... f.write(result["result"])
28
+ """
29
+
30
+ def __init__(
31
+ self,
32
+ api_key: str,
33
+ base_url: str = "https://api.bannerify.co/v1",
34
+ timeout: float = 60.0,
35
+ ):
36
+ """Initialize the Bannerify client
37
+
38
+ Args:
39
+ api_key: Your Bannerify API key
40
+ base_url: API base URL (default: https://api.bannerify.co/v1)
41
+ timeout: Request timeout in seconds (default: 60.0)
42
+ """
43
+ self.api_key = api_key
44
+ self.base_url = base_url.rstrip("/")
45
+ self.timeout = timeout
46
+ self._client = httpx.Client(
47
+ timeout=timeout,
48
+ headers={
49
+ "Authorization": f"Bearer {api_key}",
50
+ "User-Agent": "bannerify-python/0.1.0",
51
+ },
52
+ )
53
+
54
+ def __enter__(self):
55
+ return self
56
+
57
+ def __exit__(self, exc_type, exc_val, exc_tb):
58
+ self.close()
59
+
60
+ def close(self):
61
+ """Close the HTTP client"""
62
+ self._client.close()
63
+
64
+ def create_image(
65
+ self,
66
+ template_id: str,
67
+ modifications: Optional[List[Union[Modification, Dict[str, Any]]]] = None,
68
+ format: str = "png",
69
+ thumbnail: bool = False,
70
+ ) -> Dict[str, Any]:
71
+ """Generate an image from a template
72
+
73
+ Args:
74
+ template_id: Template ID (e.g., 'tpl_xxxxxxxxx')
75
+ modifications: List of modifications to apply
76
+ format: Output format - 'png' or 'svg' (default: 'png')
77
+ thumbnail: Generate thumbnail version (default: False)
78
+
79
+ Returns:
80
+ Dict with either 'result' (bytes) or 'error' (dict)
81
+
82
+ Example:
83
+ >>> result = client.create_image(
84
+ ... "tpl_xxx",
85
+ ... modifications=[{"name": "title", "text": "Hello"}],
86
+ ... format="png"
87
+ ... )
88
+ """
89
+ try:
90
+ # Convert modifications to dicts if they're Pydantic models
91
+ mods_list = []
92
+ if modifications:
93
+ for mod in modifications:
94
+ if isinstance(mod, Modification):
95
+ mods_list.append(mod.model_dump(exclude_none=True))
96
+ else:
97
+ mods_list.append(mod)
98
+
99
+ payload = {
100
+ "apiKey": self.api_key,
101
+ "templateId": template_id,
102
+ "modifications": mods_list,
103
+ "format": format,
104
+ "thumbnail": thumbnail,
105
+ }
106
+
107
+ response = self._client.post(
108
+ f"{self.base_url}/templates/createImage",
109
+ json=payload,
110
+ )
111
+
112
+ if response.status_code == 200:
113
+ content_type = response.headers.get("content-type", "")
114
+
115
+ if "image/svg" in content_type:
116
+ return {"result": response.text}
117
+ else:
118
+ return {"result": response.content}
119
+
120
+ # Handle error responses
121
+ if response.headers.get("content-type", "").startswith("application/json"):
122
+ error_data = response.json()
123
+ if "error" in error_data:
124
+ return {"error": error_data["error"]}
125
+
126
+ return self._build_error(
127
+ "HTTP_ERROR",
128
+ f"HTTP {response.status_code}: {response.text[:100]}"
129
+ )
130
+
131
+ except httpx.TimeoutException:
132
+ return self._build_error("TIMEOUT", "Request timed out")
133
+ except httpx.RequestError as e:
134
+ return self._build_error("REQUEST_ERROR", str(e))
135
+ except Exception as e:
136
+ return self._build_error("EXCEPTION", str(e))
137
+
138
+ def create_pdf(
139
+ self,
140
+ template_id: str,
141
+ modifications: Optional[List[Union[Modification, Dict[str, Any]]]] = None,
142
+ ) -> Dict[str, Any]:
143
+ """Generate a PDF from a template
144
+
145
+ Args:
146
+ template_id: Template ID
147
+ modifications: List of modifications to apply
148
+
149
+ Returns:
150
+ Dict with either 'result' (bytes) or 'error' (dict)
151
+ """
152
+ try:
153
+ # Convert modifications to dicts if they're Pydantic models
154
+ mods_list = []
155
+ if modifications:
156
+ for mod in modifications:
157
+ if isinstance(mod, Modification):
158
+ mods_list.append(mod.model_dump(exclude_none=True))
159
+ else:
160
+ mods_list.append(mod)
161
+
162
+ payload = {
163
+ "apiKey": self.api_key,
164
+ "templateId": template_id,
165
+ "modifications": mods_list,
166
+ }
167
+
168
+ response = self._client.post(
169
+ f"{self.base_url}/templates/createPdf",
170
+ json=payload,
171
+ )
172
+
173
+ if response.status_code == 200:
174
+ return {"result": response.content}
175
+
176
+ # Handle error responses
177
+ if response.headers.get("content-type", "").startswith("application/json"):
178
+ error_data = response.json()
179
+ if "error" in error_data:
180
+ return {"error": error_data["error"]}
181
+
182
+ return self._build_error(
183
+ "HTTP_ERROR",
184
+ f"HTTP {response.status_code}: {response.text[:100]}"
185
+ )
186
+
187
+ except httpx.TimeoutException:
188
+ return self._build_error("TIMEOUT", "Request timed out")
189
+ except httpx.RequestError as e:
190
+ return self._build_error("REQUEST_ERROR", str(e))
191
+ except Exception as e:
192
+ return self._build_error("EXCEPTION", str(e))
193
+
194
+ def create_stored_image(
195
+ self,
196
+ template_id: str,
197
+ modifications: Optional[List[Union[Modification, Dict[str, Any]]]] = None,
198
+ format: str = "png",
199
+ thumbnail: bool = False,
200
+ ) -> Dict[str, Any]:
201
+ """Create an image and store it on Bannerify's CDN
202
+
203
+ Args:
204
+ template_id: Template ID
205
+ modifications: List of modifications to apply
206
+ format: Output format - 'png' or 'svg'
207
+ thumbnail: Generate thumbnail version
208
+
209
+ Returns:
210
+ Dict with either 'result' (URL string) or 'error' (dict)
211
+ """
212
+ try:
213
+ # Convert modifications to dicts if they're Pydantic models
214
+ mods_list = []
215
+ if modifications:
216
+ for mod in modifications:
217
+ if isinstance(mod, Modification):
218
+ mods_list.append(mod.model_dump(exclude_none=True))
219
+ else:
220
+ mods_list.append(mod)
221
+
222
+ payload = {
223
+ "apiKey": self.api_key,
224
+ "templateId": template_id,
225
+ "modifications": mods_list,
226
+ "format": format,
227
+ "thumbnail": thumbnail,
228
+ }
229
+
230
+ response = self._client.post(
231
+ f"{self.base_url}/templates/createStoredImage",
232
+ json=payload,
233
+ )
234
+
235
+ if response.status_code == 200:
236
+ data = response.json()
237
+ return {"result": data.get("url")}
238
+
239
+ # Handle error responses
240
+ if response.headers.get("content-type", "").startswith("application/json"):
241
+ error_data = response.json()
242
+ if "error" in error_data:
243
+ return {"error": error_data["error"]}
244
+
245
+ return self._build_error(
246
+ "HTTP_ERROR",
247
+ f"HTTP {response.status_code}: {response.text[:100]}"
248
+ )
249
+
250
+ except httpx.TimeoutException:
251
+ return self._build_error("TIMEOUT", "Request timed out")
252
+ except httpx.RequestError as e:
253
+ return self._build_error("REQUEST_ERROR", str(e))
254
+ except Exception as e:
255
+ return self._build_error("EXCEPTION", str(e))
256
+
257
+ def generate_image_signed_url(
258
+ self,
259
+ template_id: str,
260
+ modifications: Optional[List[Union[Modification, Dict[str, Any]]]] = None,
261
+ format: str = "png",
262
+ thumbnail: bool = False,
263
+ nocache: bool = False,
264
+ ) -> str:
265
+ """Generate a signed URL for on-demand image generation
266
+
267
+ Args:
268
+ template_id: Template ID
269
+ modifications: List of modifications to apply
270
+ format: Output format - 'png' or 'svg'
271
+ thumbnail: Generate thumbnail version
272
+ nocache: Bypass cache
273
+
274
+ Returns:
275
+ The signed URL string
276
+
277
+ Example:
278
+ >>> url = client.generate_image_signed_url(
279
+ ... "tpl_xxx",
280
+ ... modifications=[{"name": "title", "text": "Dynamic"}]
281
+ ... )
282
+ >>> print(f"<img src='{url}' />")
283
+ """
284
+ # Hash the API key
285
+ api_key_hashed = hashlib.sha256(self.api_key.encode()).hexdigest()
286
+
287
+ params = {
288
+ "apiKeyHashed": api_key_hashed,
289
+ "templateId": template_id,
290
+ }
291
+
292
+ if format == "svg":
293
+ params["format"] = "svg"
294
+
295
+ if modifications:
296
+ # Convert modifications to dicts if they're Pydantic models
297
+ mods_list = []
298
+ for mod in modifications:
299
+ if isinstance(mod, Modification):
300
+ mods_list.append(mod.model_dump(exclude_none=True))
301
+ else:
302
+ mods_list.append(mod)
303
+ params["modifications"] = json.dumps(mods_list)
304
+
305
+ if nocache:
306
+ params["nocache"] = "true"
307
+
308
+ if thumbnail:
309
+ params["thumbnail"] = "true"
310
+
311
+ # Sort parameters
312
+ sorted_params = dict(sorted(params.items()))
313
+
314
+ # Generate signature
315
+ query_string = urlencode(sorted_params)
316
+ sign_input = query_string + api_key_hashed
317
+ sign = hashlib.sha256(sign_input.encode()).hexdigest()
318
+ sorted_params["sign"] = sign
319
+
320
+ return f"{self.base_url}/templates/signedurl?{urlencode(sorted_params)}"
321
+
322
+ def _build_error(self, code: str, message: str) -> Dict[str, Any]:
323
+ """Build an error response
324
+
325
+ Args:
326
+ code: Error code
327
+ message: Error message
328
+
329
+ Returns:
330
+ Error dict
331
+ """
332
+ return {
333
+ "error": {
334
+ "code": code,
335
+ "message": message,
336
+ "docs": "https://bannerify.co/docs",
337
+ }
338
+ }
@@ -0,0 +1,291 @@
1
+ # generated by datamodel-codegen:
2
+ # filename: openapi.json
3
+ # timestamp: 2025-11-14T19:40:03+00:00
4
+
5
+ from __future__ import annotations
6
+
7
+ from enum import Enum
8
+ from typing import Any, Dict, List, Optional
9
+
10
+ from pydantic import BaseModel, Field
11
+
12
+
13
+ class Code(Enum):
14
+ BAD_REQUEST = 'BAD_REQUEST'
15
+
16
+
17
+ class Error(BaseModel):
18
+ code: Code = Field(
19
+ ..., description='A machine readable error code.', example='BAD_REQUEST'
20
+ )
21
+ docs: str = Field(
22
+ ...,
23
+ description='A link to our documentation with more details about this error code',
24
+ example='https://documentation.bannerify.co/api-reference/errors/code/BAD_REQUEST',
25
+ )
26
+ message: str = Field(
27
+ ..., description='A human readable explanation of what went wrong'
28
+ )
29
+ requestId: str = Field(
30
+ ...,
31
+ description='Please always include the requestId in your error report',
32
+ example='req:1234',
33
+ )
34
+
35
+
36
+ class ErrBadRequest(BaseModel):
37
+ error: Error
38
+
39
+
40
+ class Code1(Enum):
41
+ FETCH_IMAGE_ERROR = 'FETCH_IMAGE_ERROR'
42
+
43
+
44
+ class Error1(BaseModel):
45
+ code: Code1 = Field(
46
+ ..., description='A machine readable error code.', example='FETCH_IMAGE_ERROR'
47
+ )
48
+ docs: str = Field(
49
+ ...,
50
+ description='A link to our documentation with more details about this error code',
51
+ example='https://documentation.bannerify.co/api-reference/errors/code/FETCH_IMAGE_ERROR',
52
+ )
53
+ message: str = Field(
54
+ ..., description='A human readable explanation of what went wrong'
55
+ )
56
+ requestId: str = Field(
57
+ ...,
58
+ description='Please always include the requestId in your error report',
59
+ example='req:1234',
60
+ )
61
+
62
+
63
+ class ErrFetchImageError(BaseModel):
64
+ error: Error1
65
+
66
+
67
+ class Code2(Enum):
68
+ UNAUTHORIZED = 'UNAUTHORIZED'
69
+
70
+
71
+ class Error2(BaseModel):
72
+ code: Code2 = Field(
73
+ ..., description='A machine readable error code.', example='UNAUTHORIZED'
74
+ )
75
+ docs: str = Field(
76
+ ...,
77
+ description='A link to our documentation with more details about this error code',
78
+ example='https://documentation.bannerify.co/api-reference/errors/code/UNAUTHORIZED',
79
+ )
80
+ message: str = Field(
81
+ ..., description='A human readable explanation of what went wrong'
82
+ )
83
+ requestId: str = Field(
84
+ ...,
85
+ description='Please always include the requestId in your error report',
86
+ example='req:1234',
87
+ )
88
+
89
+
90
+ class ErrUnauthorized(BaseModel):
91
+ error: Error2
92
+
93
+
94
+ class Code3(Enum):
95
+ FORBIDDEN = 'FORBIDDEN'
96
+
97
+
98
+ class Error3(BaseModel):
99
+ code: Code3 = Field(
100
+ ..., description='A machine readable error code.', example='FORBIDDEN'
101
+ )
102
+ docs: str = Field(
103
+ ...,
104
+ description='A link to our documentation with more details about this error code',
105
+ example='https://documentation.bannerify.co/api-reference/errors/code/FORBIDDEN',
106
+ )
107
+ message: str = Field(
108
+ ..., description='A human readable explanation of what went wrong'
109
+ )
110
+ requestId: str = Field(
111
+ ...,
112
+ description='Please always include the requestId in your error report',
113
+ example='req:1234',
114
+ )
115
+
116
+
117
+ class ErrForbidden(BaseModel):
118
+ error: Error3
119
+
120
+
121
+ class Code4(Enum):
122
+ NOT_FOUND = 'NOT_FOUND'
123
+
124
+
125
+ class Error4(BaseModel):
126
+ code: Code4 = Field(
127
+ ..., description='A machine readable error code.', example='NOT_FOUND'
128
+ )
129
+ docs: str = Field(
130
+ ...,
131
+ description='A link to our documentation with more details about this error code',
132
+ example='https://documentation.bannerify.co/api-reference/errors/code/NOT_FOUND',
133
+ )
134
+ message: str = Field(
135
+ ..., description='A human readable explanation of what went wrong'
136
+ )
137
+ requestId: str = Field(
138
+ ...,
139
+ description='Please always include the requestId in your error report',
140
+ example='req:1234',
141
+ )
142
+
143
+
144
+ class ErrNotFound(BaseModel):
145
+ error: Error4
146
+
147
+
148
+ class Code5(Enum):
149
+ CONFLICT = 'CONFLICT'
150
+
151
+
152
+ class Error5(BaseModel):
153
+ code: Code5 = Field(
154
+ ..., description='A machine readable error code.', example='CONFLICT'
155
+ )
156
+ docs: str = Field(
157
+ ...,
158
+ description='A link to our documentation with more details about this error code',
159
+ example='https://documentation.bannerify.co/api-reference/errors/code/CONFLICT',
160
+ )
161
+ message: str = Field(
162
+ ..., description='A human readable explanation of what went wrong'
163
+ )
164
+ requestId: str = Field(
165
+ ...,
166
+ description='Please always include the requestId in your error report',
167
+ example='req:1234',
168
+ )
169
+
170
+
171
+ class ErrConflict(BaseModel):
172
+ error: Error5
173
+
174
+
175
+ class Code6(Enum):
176
+ TOO_MANY_REQUESTS = 'TOO_MANY_REQUESTS'
177
+
178
+
179
+ class Error6(BaseModel):
180
+ code: Code6 = Field(
181
+ ..., description='A machine readable error code.', example='TOO_MANY_REQUESTS'
182
+ )
183
+ docs: str = Field(
184
+ ...,
185
+ description='A link to our documentation with more details about this error code',
186
+ example='https://documentation.bannerify.co/api-reference/errors/code/TOO_MANY_REQUESTS',
187
+ )
188
+ message: str = Field(
189
+ ..., description='A human readable explanation of what went wrong'
190
+ )
191
+ requestId: str = Field(
192
+ ...,
193
+ description='Please always include the requestId in your error report',
194
+ example='req:1234',
195
+ )
196
+
197
+
198
+ class ErrTooManyRequests(BaseModel):
199
+ error: Error6
200
+
201
+
202
+ class Code7(Enum):
203
+ INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR'
204
+
205
+
206
+ class Error7(BaseModel):
207
+ code: Code7 = Field(
208
+ ...,
209
+ description='A machine readable error code.',
210
+ example='INTERNAL_SERVER_ERROR',
211
+ )
212
+ docs: str = Field(
213
+ ...,
214
+ description='A link to our documentation with more details about this error code',
215
+ example='https://documentation.bannerify.co/api-reference/errors/code/INTERNAL_SERVER_ERROR',
216
+ )
217
+ message: str = Field(
218
+ ..., description='A human readable explanation of what went wrong'
219
+ )
220
+ requestId: str = Field(
221
+ ...,
222
+ description='Please always include the requestId in your error report',
223
+ example='req:1234',
224
+ )
225
+
226
+
227
+ class ErrInternalServerError(BaseModel):
228
+ error: Error7
229
+
230
+
231
+ class WidthMode(Enum):
232
+ standard = 'standard'
233
+ adaptive = 'adaptive'
234
+
235
+
236
+ class HeightMode(Enum):
237
+ standard = 'standard'
238
+ adaptive = 'adaptive'
239
+
240
+
241
+ class Theme(Enum):
242
+ NONE = 'NONE'
243
+ DEFAULT = 'DEFAULT'
244
+ BRIGHT = 'BRIGHT'
245
+ SIMPLIFY = 'SIMPLIFY'
246
+ ARCO = 'ARCO'
247
+
248
+
249
+ class Modification(BaseModel):
250
+ name: str = Field(
251
+ ..., description='The layer name of the modification', example='Text 1'
252
+ )
253
+ color: Optional[str] = Field(
254
+ None, description='The color for the modification', example='#FF0000'
255
+ )
256
+ src: Optional[str] = Field(
257
+ None,
258
+ description='The source image for the modification',
259
+ example='https://example.com/image.jpg',
260
+ )
261
+ text: Optional[str] = Field(
262
+ None,
263
+ description='You can modify the text layer with this field',
264
+ example='Hello World',
265
+ )
266
+ barcode: Optional[str] = Field(
267
+ None,
268
+ description='Modify the barcode layer content with this field',
269
+ example='1234567890',
270
+ )
271
+ qrcode: Optional[str] = Field(
272
+ None,
273
+ description='Modify the qrcode layer content with this field',
274
+ example='Some text',
275
+ )
276
+ chart: Optional[Dict[str, Any]] = Field(
277
+ None, description="Update chart layer's data, follow chart.js data structure"
278
+ )
279
+ visible: Optional[bool] = Field(
280
+ None, description='Set the visibility of the field', example=True
281
+ )
282
+ star: Optional[float] = Field(None, description='Star value', example=5)
283
+ widthMode: Optional[WidthMode] = Field(
284
+ None, description='Table width mode', example='adaptive'
285
+ )
286
+ heightMode: Optional[HeightMode] = Field(
287
+ None, description='Table height mode', example='adaptive'
288
+ )
289
+ theme: Optional[Theme] = Field(None, description='Table theme', example='NONE')
290
+ rows: Optional[List[Any]] = Field(None, description='Table rows')
291
+ columns: Optional[List[str]] = Field(None, description='Table columns')
File without changes