powerpoint-engine-api 2.0.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.
- powerpoint_engine_api-2.0.0/LICENSE +21 -0
- powerpoint_engine_api-2.0.0/PKG-INFO +154 -0
- powerpoint_engine_api-2.0.0/README.md +109 -0
- powerpoint_engine_api-2.0.0/pyproject.toml +36 -0
- powerpoint_engine_api-2.0.0/setup.cfg +4 -0
- powerpoint_engine_api-2.0.0/src/powerpoint_engine/__init__.py +30 -0
- powerpoint_engine_api-2.0.0/src/powerpoint_engine/client.py +221 -0
- powerpoint_engine_api-2.0.0/src/powerpoint_engine/exceptions.py +59 -0
- powerpoint_engine_api-2.0.0/src/powerpoint_engine_api.egg-info/PKG-INFO +154 -0
- powerpoint_engine_api-2.0.0/src/powerpoint_engine_api.egg-info/SOURCES.txt +11 -0
- powerpoint_engine_api-2.0.0/src/powerpoint_engine_api.egg-info/dependency_links.txt +1 -0
- powerpoint_engine_api-2.0.0/src/powerpoint_engine_api.egg-info/requires.txt +1 -0
- powerpoint_engine_api-2.0.0/src/powerpoint_engine_api.egg-info/top_level.txt +1 -0
|
@@ -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,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,109 @@
|
|
|
1
|
+
# PowerPoint Engine — Python SDK
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Not on PyPI yet — install from GitHub:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install git+https://github.com/powerpoint-engine-api/powerpoint-engine-python.git
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from powerpoint_engine import PowerPointEngine
|
|
17
|
+
|
|
18
|
+
engine = PowerPointEngine() # anonymous: works, but files are watermarked
|
|
19
|
+
|
|
20
|
+
result = engine.generate(markup="""
|
|
21
|
+
# Q3 Business Review
|
|
22
|
+
|
|
23
|
+
## Highlights
|
|
24
|
+
- Revenue up 24% QoQ
|
|
25
|
+
- 3 new enterprise customers
|
|
26
|
+
|
|
27
|
+
## Quarterly Numbers
|
|
28
|
+
| Region | Q1 | Q2 | Q3 |
|
|
29
|
+
|--------|-----|-----|-----|
|
|
30
|
+
| EU | 1.0 | 1.2 | 1.4 |
|
|
31
|
+
| US | 2.0 | 2.4 | 2.6 |
|
|
32
|
+
""", theme="corporate")
|
|
33
|
+
|
|
34
|
+
engine.download(result, "review.pptx")
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
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):
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
## Revenue vs Margin
|
|
41
|
+
```chart
|
|
42
|
+
type: combo
|
|
43
|
+
categories: Q1, Q2, Q3
|
|
44
|
+
Revenue: 120, 150, 170
|
|
45
|
+
Margin % (line, secondary): 10, 12, 14
|
|
46
|
+
```
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Work with existing decks
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
# Replace {{placeholders}} without breaking formatting
|
|
53
|
+
engine.replace("template.pptx", {"client": "ACME", "year": "2026"})
|
|
54
|
+
|
|
55
|
+
# Duplicate / delete / reorder slides (1-based)
|
|
56
|
+
engine.edit("deck.pptx", [
|
|
57
|
+
{"op": "duplicate", "slide": 2},
|
|
58
|
+
{"op": "delete", "slide": 5},
|
|
59
|
+
{"op": "move", "slide": 3, "to": 1},
|
|
60
|
+
])
|
|
61
|
+
|
|
62
|
+
# Merge 2-5 decks; each keeps its own design
|
|
63
|
+
engine.merge(["intro.pptx", "results.pptx", "outro.pptx"])
|
|
64
|
+
|
|
65
|
+
# Convert to PDF
|
|
66
|
+
pdf = engine.to_pdf("deck.pptx")
|
|
67
|
+
engine.download(pdf, "deck.pdf")
|
|
68
|
+
|
|
69
|
+
# Translate in place (layout preserved)
|
|
70
|
+
engine.translate("deck.pptx", "Spanish")
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Every call returns a dict with `result.downloadUrl` — a signed link valid for 24 hours. `engine.download(result, path)` fetches it.
|
|
74
|
+
|
|
75
|
+
## Branding
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
engine.generate(
|
|
79
|
+
markup="# Deck\n\n## Slide\n- point",
|
|
80
|
+
brand={"primary": "#E4002B", "secondary": "#111827"},
|
|
81
|
+
font="Georgia",
|
|
82
|
+
)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Accounts and credits
|
|
86
|
+
|
|
87
|
+
Pass your account id to tie calls to your quota and remove the watermark (Pro plan):
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
engine = PowerPointEngine(session_id="your-account-id")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
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).
|
|
94
|
+
|
|
95
|
+
## AI agents
|
|
96
|
+
|
|
97
|
+
The same engine is exposed as a remote [MCP server](https://powerpointengine.io/llms.txt) — one line in Claude Code / any MCP client:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
claude mcp add --transport http powerpoint-engine https://powerpointengine.io/api/mcp/mcp
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Errors
|
|
104
|
+
|
|
105
|
+
All errors raise `PowerPointEngineError` subclasses: `ValidationError` (400), `AuthenticationError` (401), `NotFoundError` (404), `RateLimitError` (429), `ServerError` (5xx).
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
MIT
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "powerpoint-engine-api"
|
|
7
|
+
version = "2.0.0"
|
|
8
|
+
description = "Python SDK for the PowerPoint Engine API - generate, edit, merge and PDF-convert PowerPoint decks over HTTP"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {file = "LICENSE"}
|
|
11
|
+
authors = [
|
|
12
|
+
{name = "PowerPoint Engine API", email = "support@powerpointengine.io"}
|
|
13
|
+
]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
21
|
+
"Topic :: Office/Business :: Office Suites",
|
|
22
|
+
]
|
|
23
|
+
keywords = ["powerpoint", "pptx", "presentation", "api", "sdk", "markdown", "pdf"]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"requests>=2.25.0",
|
|
26
|
+
]
|
|
27
|
+
requires-python = ">=3.8"
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://powerpointengine.io"
|
|
31
|
+
Documentation = "https://powerpointengine.io/docs"
|
|
32
|
+
Repository = "https://github.com/powerpoint-engine-api/powerpoint-engine-python"
|
|
33
|
+
"Bug Tracker" = "https://github.com/powerpoint-engine-api/powerpoint-engine-python/issues"
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.packages.find]
|
|
36
|
+
where = ["src"]
|
|
@@ -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,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/powerpoint_engine/__init__.py
|
|
5
|
+
src/powerpoint_engine/client.py
|
|
6
|
+
src/powerpoint_engine/exceptions.py
|
|
7
|
+
src/powerpoint_engine_api.egg-info/PKG-INFO
|
|
8
|
+
src/powerpoint_engine_api.egg-info/SOURCES.txt
|
|
9
|
+
src/powerpoint_engine_api.egg-info/dependency_links.txt
|
|
10
|
+
src/powerpoint_engine_api.egg-info/requires.txt
|
|
11
|
+
src/powerpoint_engine_api.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests>=2.25.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
powerpoint_engine
|