powerpoint-engine-api 2.0.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.
@@ -0,0 +1,30 @@
1
+ """PowerPoint Engine API Python SDK
2
+
3
+ A Python client for the PowerPoint Engine API (https://powerpointengine.io):
4
+ generate .pptx from Markdown or a template, edit/merge/replace in existing
5
+ decks, and convert PPTX to PDF.
6
+ """
7
+
8
+ __version__ = "2.0.0"
9
+ __author__ = "PowerPoint Engine API"
10
+ __email__ = "support@powerpointengine.io"
11
+
12
+ from .client import PowerPointEngine
13
+ from .exceptions import (
14
+ PowerPointEngineError,
15
+ AuthenticationError,
16
+ ValidationError,
17
+ NotFoundError,
18
+ RateLimitError,
19
+ ServerError,
20
+ )
21
+
22
+ __all__ = [
23
+ "PowerPointEngine",
24
+ "PowerPointEngineError",
25
+ "AuthenticationError",
26
+ "ValidationError",
27
+ "NotFoundError",
28
+ "RateLimitError",
29
+ "ServerError",
30
+ ]
@@ -0,0 +1,221 @@
1
+ """PowerPoint Engine API client.
2
+
3
+ Thin wrapper over the public REST API at https://powerpointengine.io.
4
+ All methods return the parsed JSON response; generated files are fetched
5
+ from the signed ``downloadUrl`` in the result (valid for 24 hours).
6
+ """
7
+
8
+ import json
9
+ import mimetypes
10
+ import os
11
+ from typing import Any, Dict, List, Optional
12
+
13
+ import requests
14
+
15
+ from .exceptions import (
16
+ AuthenticationError,
17
+ NotFoundError,
18
+ PowerPointEngineError,
19
+ RateLimitError,
20
+ ServerError,
21
+ ValidationError,
22
+ )
23
+
24
+ DEFAULT_BASE_URL = "https://powerpointengine.io"
25
+
26
+ PPTX_MIME = (
27
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation"
28
+ )
29
+
30
+
31
+ def _file_part(path):
32
+ """(filename, handle, mime) tuple so the API sees the right content type."""
33
+ mime = mimetypes.guess_type(path)[0] or PPTX_MIME
34
+ return (os.path.basename(path), open(path, "rb"), mime)
35
+
36
+
37
+ class PowerPointEngine:
38
+ """Client for the PowerPoint Engine API.
39
+
40
+ Args:
41
+ session_id: Optional account id (the ``sessionId`` the dashboard
42
+ shows). Ties generations to your account for credits and history;
43
+ anonymous calls work but return watermarked files.
44
+ base_url: API origin (default: https://powerpointengine.io).
45
+ timeout: Per-request timeout in seconds.
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ session_id: Optional[str] = None,
51
+ base_url: str = DEFAULT_BASE_URL,
52
+ timeout: int = 120,
53
+ ):
54
+ self.session_id = session_id
55
+ self.base_url = base_url.rstrip("/")
56
+ self.timeout = timeout
57
+ self.session = requests.Session()
58
+ self.session.headers["User-Agent"] = "powerpoint-engine-python/2.0.0"
59
+
60
+ # -- generation ---------------------------------------------------------
61
+
62
+ def generate(
63
+ self,
64
+ markup: Optional[str] = None,
65
+ template: Optional[Dict[str, Any]] = None,
66
+ theme: str = "corporate",
67
+ brand: Optional[Dict[str, str]] = None,
68
+ font: Optional[str] = None,
69
+ ) -> Dict[str, Any]:
70
+ """Generate a .pptx from markup text or a structured template.
71
+
72
+ Exactly one of ``markup`` / ``template`` is required. ``markup`` is
73
+ the Markdown dialect (``# title``, ``## slide``, bullets, tables,
74
+ ```` ```chart ```` blocks). ``brand`` is a dict of 6-hex colors like
75
+ ``{"primary": "#E4002B"}``; ``font`` overrides the theme font.
76
+ """
77
+ if not markup and not template:
78
+ raise ValueError("either markup or template is required")
79
+ body: Dict[str, Any] = {"theme": theme}
80
+ if markup:
81
+ body["markup"] = markup
82
+ if template:
83
+ body["template"] = template
84
+ if brand:
85
+ body["brand"] = brand
86
+ if font:
87
+ body["font"] = font
88
+ if self.session_id:
89
+ body["sessionId"] = self.session_id
90
+ return self._request_json("/api/powerpoint/generate", body)
91
+
92
+ # -- operations on an existing .pptx ------------------------------------
93
+
94
+ def replace(
95
+ self,
96
+ file_path: str,
97
+ replacements: Dict[str, str],
98
+ replace_mode: str = "placeholders",
99
+ images: Optional[Dict[str, str]] = None,
100
+ ) -> Dict[str, Any]:
101
+ """Replace text (and optionally images) in your own .pptx in place.
102
+
103
+ ``replace_mode`` is ``placeholders`` ({{key}} tokens) or ``objects``
104
+ (match shapes by name). ``images`` maps a shape name or alt text to a
105
+ local image path; position, size and effects are kept.
106
+ """
107
+ data = {
108
+ "replacements": json.dumps(replacements),
109
+ "replaceMode": replace_mode,
110
+ }
111
+ files = {"file": _file_part(file_path)}
112
+ try:
113
+ if images:
114
+ for shape_name, image_path in images.items():
115
+ files[f"image:{shape_name}"] = _file_part(image_path)
116
+ return self._request_multipart("/api/powerpoint/replace", data, files)
117
+ finally:
118
+ for part in files.values():
119
+ part[1].close()
120
+
121
+ def edit(self, file_path: str, operations: List[Dict[str, Any]]) -> Dict[str, Any]:
122
+ """Duplicate / delete / move slides in your own .pptx.
123
+
124
+ ``operations`` apply sequentially, slide numbers are 1-based, e.g.::
125
+
126
+ [{"op": "duplicate", "slide": 2},
127
+ {"op": "delete", "slide": 5},
128
+ {"op": "move", "slide": 3, "to": 1}]
129
+ """
130
+ data = {"operations": json.dumps(operations)}
131
+ part = _file_part(file_path)
132
+ try:
133
+ return self._request_multipart(
134
+ "/api/powerpoint/edit", data, {"file": part}
135
+ )
136
+ finally:
137
+ part[1].close()
138
+
139
+ def merge(self, file_paths: List[str]) -> Dict[str, Any]:
140
+ """Merge 2-5 decks into one; each keeps its own design."""
141
+ if not 2 <= len(file_paths) <= 5:
142
+ raise ValueError("merge takes 2 to 5 files")
143
+ parts = [_file_part(p) for p in file_paths]
144
+ try:
145
+ files = [("files", part) for part in parts]
146
+ return self._request_multipart("/api/powerpoint/merge", {}, files)
147
+ finally:
148
+ for part in parts:
149
+ part[1].close()
150
+
151
+ def to_pdf(self, file_path: str) -> Dict[str, Any]:
152
+ """Convert a .pptx to PDF; result has a signed PDF downloadUrl."""
153
+ part = _file_part(file_path)
154
+ try:
155
+ return self._request_multipart(
156
+ "/api/powerpoint/pdf", {}, {"file": part}
157
+ )
158
+ finally:
159
+ part[1].close()
160
+
161
+ def translate(self, file_path: str, target_lang: str) -> Dict[str, Any]:
162
+ """Translate all text in a .pptx in place (layout preserved)."""
163
+ data = {"targetLang": target_lang}
164
+ part = _file_part(file_path)
165
+ try:
166
+ return self._request_multipart(
167
+ "/api/powerpoint/translate", data, {"file": part}
168
+ )
169
+ finally:
170
+ part[1].close()
171
+
172
+ # -- helpers -------------------------------------------------------------
173
+
174
+ def download(self, result: Dict[str, Any], to_path: str) -> str:
175
+ """Download the generated file from a response's downloadUrl."""
176
+ url = (result.get("result") or {}).get("downloadUrl") or result.get(
177
+ "downloadUrl"
178
+ )
179
+ if not url:
180
+ raise ValueError("response has no downloadUrl")
181
+ response = requests.get(url, timeout=self.timeout)
182
+ response.raise_for_status()
183
+ with open(to_path, "wb") as fh:
184
+ fh.write(response.content)
185
+ return to_path
186
+
187
+ def _request_json(self, endpoint: str, body: Dict[str, Any]) -> Dict[str, Any]:
188
+ response = self.session.post(
189
+ self.base_url + endpoint, json=body, timeout=self.timeout
190
+ )
191
+ return self._parse(response)
192
+
193
+ def _request_multipart(self, endpoint: str, data, files) -> Dict[str, Any]:
194
+ if self.session_id:
195
+ data = dict(data)
196
+ data["sessionId"] = self.session_id
197
+ response = self.session.post(
198
+ self.base_url + endpoint, data=data, files=files, timeout=self.timeout
199
+ )
200
+ return self._parse(response)
201
+
202
+ def _parse(self, response: requests.Response) -> Dict[str, Any]:
203
+ try:
204
+ payload = response.json()
205
+ except ValueError:
206
+ payload = {"error": response.text[:500]}
207
+ if response.ok:
208
+ return payload
209
+ message = payload.get("error", "Unknown error")
210
+ status = response.status_code
211
+ if status == 401:
212
+ raise AuthenticationError(message, status)
213
+ if status == 400:
214
+ raise ValidationError(message, status)
215
+ if status == 404:
216
+ raise NotFoundError(message, status)
217
+ if status == 429:
218
+ raise RateLimitError(message, status)
219
+ if status >= 500:
220
+ raise ServerError(message, status)
221
+ raise PowerPointEngineError(message, status)
@@ -0,0 +1,59 @@
1
+ """PowerPoint Engine API Exceptions"""
2
+
3
+ from typing import Optional
4
+
5
+
6
+ class PowerPointEngineError(Exception):
7
+ """Base exception for PowerPoint Engine API errors."""
8
+
9
+ def __init__(
10
+ self,
11
+ message: str,
12
+ status_code: Optional[int] = None,
13
+ request_id: Optional[str] = None,
14
+ ):
15
+ """Initialize the exception.
16
+
17
+ Args:
18
+ message: Error message
19
+ status_code: HTTP status code
20
+ request_id: Request ID for debugging
21
+ """
22
+ super().__init__(message)
23
+ self.message = message
24
+ self.status_code = status_code
25
+ self.request_id = request_id
26
+
27
+ def __str__(self) -> str:
28
+ """String representation of the error."""
29
+ parts = [self.message]
30
+ if self.status_code:
31
+ parts.append(f"Status: {self.status_code}")
32
+ if self.request_id:
33
+ parts.append(f"Request ID: {self.request_id}")
34
+ return " | ".join(parts)
35
+
36
+
37
+ class AuthenticationError(PowerPointEngineError):
38
+ """Raised when API key is invalid or missing."""
39
+ pass
40
+
41
+
42
+ class ValidationError(PowerPointEngineError):
43
+ """Raised when request data is invalid."""
44
+ pass
45
+
46
+
47
+ class NotFoundError(PowerPointEngineError):
48
+ """Raised when requested resource is not found."""
49
+ pass
50
+
51
+
52
+ class RateLimitError(PowerPointEngineError):
53
+ """Raised when rate limit is exceeded."""
54
+ pass
55
+
56
+
57
+ class ServerError(PowerPointEngineError):
58
+ """Raised when server encounters an error."""
59
+ pass
@@ -0,0 +1,154 @@
1
+ Metadata-Version: 2.4
2
+ Name: powerpoint-engine-api
3
+ Version: 2.0.0
4
+ Summary: Python SDK for the PowerPoint Engine API - generate, edit, merge and PDF-convert PowerPoint decks over HTTP
5
+ Author-email: PowerPoint Engine API <support@powerpointengine.io>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2024 PowerPoint Engine API
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://powerpointengine.io
29
+ Project-URL: Documentation, https://powerpointengine.io/docs
30
+ Project-URL: Repository, https://github.com/powerpoint-engine-api/powerpoint-engine-python
31
+ Project-URL: Bug Tracker, https://github.com/powerpoint-engine-api/powerpoint-engine-python/issues
32
+ Keywords: powerpoint,pptx,presentation,api,sdk,markdown,pdf
33
+ Classifier: Development Status :: 4 - Beta
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Operating System :: OS Independent
37
+ Classifier: Programming Language :: Python :: 3
38
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
39
+ Classifier: Topic :: Office/Business :: Office Suites
40
+ Requires-Python: >=3.8
41
+ Description-Content-Type: text/markdown
42
+ License-File: LICENSE
43
+ Requires-Dist: requests>=2.25.0
44
+ Dynamic: license-file
45
+
46
+ # PowerPoint Engine — Python SDK
47
+
48
+ Python client for the [PowerPoint Engine API](https://powerpointengine.io): generate `.pptx` from Markdown or a structured template, edit / merge / replace inside existing decks, and convert PPTX to PDF — over plain HTTP, no PowerPoint or LibreOffice install needed.
49
+
50
+ ## Install
51
+
52
+ Not on PyPI yet — install from GitHub:
53
+
54
+ ```bash
55
+ pip install git+https://github.com/powerpoint-engine-api/powerpoint-engine-python.git
56
+ ```
57
+
58
+ ## Quick start
59
+
60
+ ```python
61
+ from powerpoint_engine import PowerPointEngine
62
+
63
+ engine = PowerPointEngine() # anonymous: works, but files are watermarked
64
+
65
+ result = engine.generate(markup="""
66
+ # Q3 Business Review
67
+
68
+ ## Highlights
69
+ - Revenue up 24% QoQ
70
+ - 3 new enterprise customers
71
+
72
+ ## Quarterly Numbers
73
+ | Region | Q1 | Q2 | Q3 |
74
+ |--------|-----|-----|-----|
75
+ | EU | 1.0 | 1.2 | 1.4 |
76
+ | US | 2.0 | 2.4 | 2.6 |
77
+ """, theme="corporate")
78
+
79
+ engine.download(result, "review.pptx")
80
+ ```
81
+
82
+ Markdown tables become native PowerPoint tables. A fenced ```` ```chart ```` block under a `## heading` becomes a real editable chart (col / bar / line / pie / combo with a secondary axis):
83
+
84
+ ```text
85
+ ## Revenue vs Margin
86
+ ```chart
87
+ type: combo
88
+ categories: Q1, Q2, Q3
89
+ Revenue: 120, 150, 170
90
+ Margin % (line, secondary): 10, 12, 14
91
+ ```
92
+ ```
93
+
94
+ ## Work with existing decks
95
+
96
+ ```python
97
+ # Replace {{placeholders}} without breaking formatting
98
+ engine.replace("template.pptx", {"client": "ACME", "year": "2026"})
99
+
100
+ # Duplicate / delete / reorder slides (1-based)
101
+ engine.edit("deck.pptx", [
102
+ {"op": "duplicate", "slide": 2},
103
+ {"op": "delete", "slide": 5},
104
+ {"op": "move", "slide": 3, "to": 1},
105
+ ])
106
+
107
+ # Merge 2-5 decks; each keeps its own design
108
+ engine.merge(["intro.pptx", "results.pptx", "outro.pptx"])
109
+
110
+ # Convert to PDF
111
+ pdf = engine.to_pdf("deck.pptx")
112
+ engine.download(pdf, "deck.pdf")
113
+
114
+ # Translate in place (layout preserved)
115
+ engine.translate("deck.pptx", "Spanish")
116
+ ```
117
+
118
+ Every call returns a dict with `result.downloadUrl` — a signed link valid for 24 hours. `engine.download(result, path)` fetches it.
119
+
120
+ ## Branding
121
+
122
+ ```python
123
+ engine.generate(
124
+ markup="# Deck\n\n## Slide\n- point",
125
+ brand={"primary": "#E4002B", "secondary": "#111827"},
126
+ font="Georgia",
127
+ )
128
+ ```
129
+
130
+ ## Accounts and credits
131
+
132
+ Pass your account id to tie calls to your quota and remove the watermark (Pro plan):
133
+
134
+ ```python
135
+ engine = PowerPointEngine(session_id="your-account-id")
136
+ ```
137
+
138
+ Billing is credit-based: 1 credit = up to 10 slides. Free tier: 5 credits/month (watermarked); Pro ($9/mo) removes the watermark. Details: [powerpointengine.io/#pricing](https://powerpointengine.io/#pricing).
139
+
140
+ ## AI agents
141
+
142
+ The same engine is exposed as a remote [MCP server](https://powerpointengine.io/llms.txt) — one line in Claude Code / any MCP client:
143
+
144
+ ```bash
145
+ claude mcp add --transport http powerpoint-engine https://powerpointengine.io/api/mcp/mcp
146
+ ```
147
+
148
+ ## Errors
149
+
150
+ All errors raise `PowerPointEngineError` subclasses: `ValidationError` (400), `AuthenticationError` (401), `NotFoundError` (404), `RateLimitError` (429), `ServerError` (5xx).
151
+
152
+ ## License
153
+
154
+ MIT
@@ -0,0 +1,8 @@
1
+ powerpoint_engine/__init__.py,sha256=kCLao6-mM0fgmSm1NAtKGelgg6nuVanf0XFTtaJ9mes,702
2
+ powerpoint_engine/client.py,sha256=qgIxrhqxn2G_JOIGWbhl0qK8SbPqs-43Ookr3hcN1hE,7943
3
+ powerpoint_engine/exceptions.py,sha256=0IRGAw0EObKki60PTpgH-VY111oJqaW1x2xzcraKpxQ,1512
4
+ powerpoint_engine_api-2.0.0.dist-info/licenses/LICENSE,sha256=O-hs3lV0uUmp-mfG-iOHM7Kop2vYkDZMSwA9V0Hc3_Y,1078
5
+ powerpoint_engine_api-2.0.0.dist-info/METADATA,sha256=3Id6q3Y4NxBIxCo_JUZfam6OxIe04VnP1Tb9bn0eOnU,5419
6
+ powerpoint_engine_api-2.0.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
7
+ powerpoint_engine_api-2.0.0.dist-info/top_level.txt,sha256=pPivEgmcQq7w7vAR5pzIIkWGzGbHWnij1YpmeR2QJVE,18
8
+ powerpoint_engine_api-2.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 PowerPoint Engine API
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ powerpoint_engine