bannerify 0.1.0__py3-none-any.whl

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.
bannerify/__init__.py ADDED
@@ -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"]
bannerify/client.py ADDED
@@ -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
+ }
bannerify/models.py ADDED
@@ -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')
bannerify/py.typed ADDED
File without changes
@@ -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,7 @@
1
+ bannerify/__init__.py,sha256=j8p4JZ9H_IlqKkXMX2it48Wh7PuxX7suPk9V9N-A54U,202
2
+ bannerify/client.py,sha256=rdFCGxTiRQ0RzH0ziWUoUpTAR5g150gz0Rwl-omtozc,11249
3
+ bannerify/models.py,sha256=AidUt1-E1LLg5iBv4S7FNPsTMCYr_Xd6dXIiDKnxSe8,8153
4
+ bannerify/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ bannerify-0.1.0.dist-info/WHEEL,sha256=n2u5OFBbdZvCiUKAmfnY1Po2j3FB_NWfuUlt5WiAjrk,79
6
+ bannerify-0.1.0.dist-info/METADATA,sha256=PRHX3WGbod2g4daRUTdc69yTSfBwYyAVTllWvqNYzr0,6096
7
+ bannerify-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.8.23
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any