athena-python-pptx 0.4.0__tar.gz → 0.4.2__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.
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/CHANGELOG.md +25 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/CLAUDE.md +3 -2
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/DEV-GUIDE.md +3 -3
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/PKG-INFO +16 -13
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/README.md +15 -12
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/docs/API_PARITY_EXCEPTIONS.md +47 -19
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/__init__.py +4 -3
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/client.py +1 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/commands.py +60 -54
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/presentation.py +69 -34
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/shapes/__init__.py +9 -4
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/text/__init__.py +176 -27
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/typing.py +4 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pyproject.toml +1 -1
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/uv.lock +20 -20
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/.gitignore +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/API_PARITY_REPORT.md +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/PARITY_QUESTIONS.md +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/PUBLISHING.md +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/docs/athena-api.json +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/docs/athena-api.md +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/_athena_extension.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/_ptc.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/_references.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/action.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/batching.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/chart/__init__.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/chart/axis.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/chart/category.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/chart/chart.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/chart/data.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/chart/datalabel.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/chart/legend.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/chart/marker.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/chart/plot.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/chart/point.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/chart/series.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/chart/xlsx.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/decorators.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/dml/__init__.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/dml/chtfmt.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/dml/color.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/dml/effect.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/dml/fill.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/dml/line.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/docgen.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/enum/__init__.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/enum/action.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/enum/chart.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/enum/dml.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/enum/lang.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/enum/shapes.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/enum/text.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/errors.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/exc.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/media.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/package.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/parts/__init__.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/parts/_base.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/parts/chart.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/parts/coreprops.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/parts/embeddedpackage.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/parts/image.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/parts/media.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/parts/presentation.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/parts/slide.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/shapes/autoshape.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/shapes/base.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/shapes/connector.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/shapes/freeform.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/shapes/graphfrm.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/shapes/group.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/shapes/picture.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/shapes/placeholder.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/shapes/shapetree.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/shared.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/slide.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/slides.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/spec.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/table.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/text/fonts.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/text/layout.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/text/text.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/types.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/units.py +0 -0
- {athena_python_pptx-0.4.0 → athena_python_pptx-0.4.2}/pptx/util.py +0 -0
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `athena-python-pptx` are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.4.2
|
|
6
|
+
|
|
7
|
+
**Paragraph-level font fidelity — `paragraph.font` now matches stock
|
|
8
|
+
python-pptx `defRPr` semantics (fidelity-pipeline findings batch).**
|
|
9
|
+
|
|
10
|
+
- **`paragraph.font` is a real paragraph-level font.** Previously it
|
|
11
|
+
returned the first run's font ("approximate by targeting the first
|
|
12
|
+
run"), which (a) produced exports with explicit `<a:rPr>` on every run
|
|
13
|
+
where stock python-pptx writes `<a:pPr><a:defRPr/>` (the fidelity
|
|
14
|
+
pipeline's sdk-projection leg counted 229 structural diffs on the
|
|
15
|
+
25-slide diagnostic deck) and (b) mis-styled multi-run paragraphs (only
|
|
16
|
+
run 0 changed, where stock applies the default to ALL runs). Writes now
|
|
17
|
+
emit `SetParagraphStyle` with a `defaultRunStyle` payload; the studio
|
|
18
|
+
stores it as the paragraph's authored defaults and exports `defRPr`
|
|
19
|
+
exactly like stock.
|
|
20
|
+
- **`run.font.color.brightness` now round-trips.** Previously stored
|
|
21
|
+
locally and silently dropped; now emitted with the theme color and
|
|
22
|
+
serialized as `lumMod`/`lumOff` modifiers on `<a:schemeClr>` using
|
|
23
|
+
python-pptx's mapping.
|
|
24
|
+
- Studio-side (same batch): theme color references (`schemeColor`) now
|
|
25
|
+
survive on SDK-created textbox runs, and new paragraphs added via
|
|
26
|
+
`add_paragraph()`/multi-line `tf.text` no longer inherit the previous
|
|
27
|
+
paragraph's authored level/spacing/alignment (server-side paragraph
|
|
28
|
+
template bleed).
|
|
29
|
+
|
|
5
30
|
## 0.3.1
|
|
6
31
|
|
|
7
32
|
**Chart title runs, slide insertion, and shape-delete local state — Insight
|
|
@@ -45,8 +45,9 @@ A small number of REST-SDK-specific departures are documented in
|
|
|
45
45
|
[`docs/API_PARITY_EXCEPTIONS.md`](docs/API_PARITY_EXCEPTIONS.md):
|
|
46
46
|
|
|
47
47
|
- **`Presentation(filename)` not supported** — REST SDK uses
|
|
48
|
-
`Presentation.upload(path)` or `Presentation(
|
|
49
|
-
there's no local OPC
|
|
48
|
+
`Presentation.upload(path)` or `Presentation(asset_id=…)` instead
|
|
49
|
+
(`deck_id=` is the accepted legacy alias); there's no local OPC
|
|
50
|
+
package to open.
|
|
50
51
|
- **`NotesSlide.shapes` / `.placeholders` / `.notes_placeholder`** not yet exposed —
|
|
51
52
|
use `slide.notes_slide.notes_text_frame` for notes text.
|
|
52
53
|
- **`XyChartData.add_series(name, values)`** signature drift vs upstream's
|
|
@@ -23,9 +23,9 @@ Create `scratch.py` in the `python-sdk/` directory:
|
|
|
23
23
|
```python
|
|
24
24
|
from pptx import Presentation
|
|
25
25
|
|
|
26
|
-
# Upload a test file or use an existing
|
|
26
|
+
# Upload a test file or use an existing asset id
|
|
27
27
|
prs = Presentation.upload("path/to/test.pptx", name="test")
|
|
28
|
-
print(f"
|
|
28
|
+
print(f"Presentation: {prs.asset_id}, Slides: {prs.slide_count}")
|
|
29
29
|
|
|
30
30
|
slide = prs.slides[0]
|
|
31
31
|
for shape in slide.shapes:
|
|
@@ -100,5 +100,5 @@ The SDK is a **remote proxy** over the PPTX Studio API:
|
|
|
100
100
|
| Method | Example |
|
|
101
101
|
|--------|---------|
|
|
102
102
|
| Env vars | `ATHENA_PPTX_BASE_URL=http://localhost:4000` |
|
|
103
|
-
| Explicit | `Presentation(
|
|
103
|
+
| Explicit | `Presentation(asset_id="asset_...", base_url="http://localhost:4000")` (`deck_id=` is the legacy alias) |
|
|
104
104
|
| API key (optional) | `ATHENA_PPTX_API_KEY=your-key` |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: athena-python-pptx
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Drop-in replacement for python-pptx that connects to PPTX Studio for real-time collaboration
|
|
5
5
|
Project-URL: Homepage, https://github.com/pptx-studio/python-sdk
|
|
6
6
|
Project-URL: Documentation, https://docs.pptx-studio.com/sdk/python
|
|
@@ -202,7 +202,7 @@ export ATHENA_PPTX_API_KEY=your-api-key # Optional
|
|
|
202
202
|
Or pass them explicitly:
|
|
203
203
|
|
|
204
204
|
```python
|
|
205
|
-
prs = Presentation(
|
|
205
|
+
prs = Presentation(asset_id="asset_3a9328bc-9c1c-4498-be8f-bda3883276f5", base_url="...", api_key="...")
|
|
206
206
|
```
|
|
207
207
|
|
|
208
208
|
## Quick Start
|
|
@@ -246,24 +246,27 @@ slide.shapes[0].text_frame.text = "Updated Title"
|
|
|
246
246
|
prs.save("modified.pptx")
|
|
247
247
|
```
|
|
248
248
|
|
|
249
|
-
### Connect to an Existing
|
|
249
|
+
### Connect to an Existing Presentation
|
|
250
250
|
|
|
251
251
|
```python
|
|
252
252
|
from pptx import Presentation
|
|
253
253
|
|
|
254
|
-
# Connect to a
|
|
255
|
-
prs = Presentation(
|
|
254
|
+
# Connect to a presentation by its Athena asset id
|
|
255
|
+
prs = Presentation(asset_id="asset_3a9328bc-9c1c-4498-be8f-bda3883276f5")
|
|
256
256
|
|
|
257
257
|
# Work with slides
|
|
258
258
|
for slide in prs.slides:
|
|
259
259
|
print(f"Slide {slide.slide_index}: {len(slide.shapes)} shapes")
|
|
260
260
|
```
|
|
261
261
|
|
|
262
|
-
|
|
262
|
+
`deck_id=` is accepted as a legacy alias for `asset_id=` (it is the same
|
|
263
|
+
identifier string); prefer `asset_id=` in new code.
|
|
264
|
+
|
|
265
|
+
> **Important:** When working with an existing presentation, you **MUST** include the `asset_id` parameter:
|
|
263
266
|
> ```python
|
|
264
|
-
> prs = Presentation(
|
|
267
|
+
> prs = Presentation(asset_id="asset_d822b6e3-0a73-4214-9e71-8f28a3f7c9d9")
|
|
265
268
|
> ```
|
|
266
|
-
> Without `
|
|
269
|
+
> Without `asset_id`, the SDK cannot connect to PPTX Studio and will fail.
|
|
267
270
|
|
|
268
271
|
---
|
|
269
272
|
|
|
@@ -271,7 +274,7 @@ for slide in prs.slides:
|
|
|
271
274
|
|
|
272
275
|
### Presentation
|
|
273
276
|
|
|
274
|
-
The main entry point for working with a
|
|
277
|
+
The main entry point for working with a presentation.
|
|
275
278
|
|
|
276
279
|
#### Class Methods
|
|
277
280
|
|
|
@@ -286,14 +289,14 @@ prs = Presentation.open("path/to/file.pptx") # Alias for upload()
|
|
|
286
289
|
# Connect from a full URL
|
|
287
290
|
prs = Presentation.from_url("https://api.example.com/decks/deck_123")
|
|
288
291
|
|
|
289
|
-
# Connect to an existing
|
|
290
|
-
prs = Presentation(
|
|
292
|
+
# Connect to an existing presentation by asset id
|
|
293
|
+
prs = Presentation(asset_id="asset_3a9328bc-9c1c-4498-be8f-bda3883276f5")
|
|
291
294
|
```
|
|
292
295
|
|
|
293
296
|
#### Properties
|
|
294
297
|
|
|
295
298
|
```python
|
|
296
|
-
prs.
|
|
299
|
+
prs.asset_id # str: Athena asset id (prs.deck_id is the legacy alias)
|
|
297
300
|
prs.slides # Slides: Collection of slides
|
|
298
301
|
prs.slide_width # Emu: Width of slides
|
|
299
302
|
prs.slide_height # Emu: Height of slides
|
|
@@ -889,7 +892,7 @@ print(info['authToken'])
|
|
|
889
892
|
|
|
890
893
|
See [`docs/API_PARITY_EXCEPTIONS.md`](docs/API_PARITY_EXCEPTIONS.md) for the full list. Highlights:
|
|
891
894
|
|
|
892
|
-
- **`Presentation(filename)` not supported** — use `Presentation.upload(path)` or `Presentation(
|
|
895
|
+
- **`Presentation(filename)` not supported** — use `Presentation.upload(path)` or `Presentation(asset_id=…)` instead (`deck_id=` is the legacy alias).
|
|
893
896
|
- **`NotesSlide.shapes` / `.placeholders` / `.notes_placeholder`** not yet exposed — use `slide.notes_slide.notes_text_frame` for text.
|
|
894
897
|
- **`XyChartData.add_series(name, values)`** signature drift vs upstream's `add_series(name, number_format=None)`.
|
|
895
898
|
- **`TextFitter.best_fit_font_size()`** returns `max_size` unchanged (auto-fit is server-side).
|
|
@@ -162,7 +162,7 @@ export ATHENA_PPTX_API_KEY=your-api-key # Optional
|
|
|
162
162
|
Or pass them explicitly:
|
|
163
163
|
|
|
164
164
|
```python
|
|
165
|
-
prs = Presentation(
|
|
165
|
+
prs = Presentation(asset_id="asset_3a9328bc-9c1c-4498-be8f-bda3883276f5", base_url="...", api_key="...")
|
|
166
166
|
```
|
|
167
167
|
|
|
168
168
|
## Quick Start
|
|
@@ -206,24 +206,27 @@ slide.shapes[0].text_frame.text = "Updated Title"
|
|
|
206
206
|
prs.save("modified.pptx")
|
|
207
207
|
```
|
|
208
208
|
|
|
209
|
-
### Connect to an Existing
|
|
209
|
+
### Connect to an Existing Presentation
|
|
210
210
|
|
|
211
211
|
```python
|
|
212
212
|
from pptx import Presentation
|
|
213
213
|
|
|
214
|
-
# Connect to a
|
|
215
|
-
prs = Presentation(
|
|
214
|
+
# Connect to a presentation by its Athena asset id
|
|
215
|
+
prs = Presentation(asset_id="asset_3a9328bc-9c1c-4498-be8f-bda3883276f5")
|
|
216
216
|
|
|
217
217
|
# Work with slides
|
|
218
218
|
for slide in prs.slides:
|
|
219
219
|
print(f"Slide {slide.slide_index}: {len(slide.shapes)} shapes")
|
|
220
220
|
```
|
|
221
221
|
|
|
222
|
-
|
|
222
|
+
`deck_id=` is accepted as a legacy alias for `asset_id=` (it is the same
|
|
223
|
+
identifier string); prefer `asset_id=` in new code.
|
|
224
|
+
|
|
225
|
+
> **Important:** When working with an existing presentation, you **MUST** include the `asset_id` parameter:
|
|
223
226
|
> ```python
|
|
224
|
-
> prs = Presentation(
|
|
227
|
+
> prs = Presentation(asset_id="asset_d822b6e3-0a73-4214-9e71-8f28a3f7c9d9")
|
|
225
228
|
> ```
|
|
226
|
-
> Without `
|
|
229
|
+
> Without `asset_id`, the SDK cannot connect to PPTX Studio and will fail.
|
|
227
230
|
|
|
228
231
|
---
|
|
229
232
|
|
|
@@ -231,7 +234,7 @@ for slide in prs.slides:
|
|
|
231
234
|
|
|
232
235
|
### Presentation
|
|
233
236
|
|
|
234
|
-
The main entry point for working with a
|
|
237
|
+
The main entry point for working with a presentation.
|
|
235
238
|
|
|
236
239
|
#### Class Methods
|
|
237
240
|
|
|
@@ -246,14 +249,14 @@ prs = Presentation.open("path/to/file.pptx") # Alias for upload()
|
|
|
246
249
|
# Connect from a full URL
|
|
247
250
|
prs = Presentation.from_url("https://api.example.com/decks/deck_123")
|
|
248
251
|
|
|
249
|
-
# Connect to an existing
|
|
250
|
-
prs = Presentation(
|
|
252
|
+
# Connect to an existing presentation by asset id
|
|
253
|
+
prs = Presentation(asset_id="asset_3a9328bc-9c1c-4498-be8f-bda3883276f5")
|
|
251
254
|
```
|
|
252
255
|
|
|
253
256
|
#### Properties
|
|
254
257
|
|
|
255
258
|
```python
|
|
256
|
-
prs.
|
|
259
|
+
prs.asset_id # str: Athena asset id (prs.deck_id is the legacy alias)
|
|
257
260
|
prs.slides # Slides: Collection of slides
|
|
258
261
|
prs.slide_width # Emu: Width of slides
|
|
259
262
|
prs.slide_height # Emu: Height of slides
|
|
@@ -849,7 +852,7 @@ print(info['authToken'])
|
|
|
849
852
|
|
|
850
853
|
See [`docs/API_PARITY_EXCEPTIONS.md`](docs/API_PARITY_EXCEPTIONS.md) for the full list. Highlights:
|
|
851
854
|
|
|
852
|
-
- **`Presentation(filename)` not supported** — use `Presentation.upload(path)` or `Presentation(
|
|
855
|
+
- **`Presentation(filename)` not supported** — use `Presentation.upload(path)` or `Presentation(asset_id=…)` instead (`deck_id=` is the legacy alias).
|
|
853
856
|
- **`NotesSlide.shapes` / `.placeholders` / `.notes_placeholder`** not yet exposed — use `slide.notes_slide.notes_text_frame` for text.
|
|
854
857
|
- **`XyChartData.add_series(name, values)`** signature drift vs upstream's `add_series(name, number_format=None)`.
|
|
855
858
|
- **`TextFitter.best_fit_font_size()`** returns `max_size` unchanged (auto-fit is server-side).
|
|
@@ -352,19 +352,30 @@ public reorder API (callers manipulate `slides.sldIdLst` XML directly).
|
|
|
352
352
|
prs.reorder_slides([2, 0, 1]) # move slide 2 to the front
|
|
353
353
|
```
|
|
354
354
|
|
|
355
|
-
###
|
|
356
|
-
|
|
357
|
-
`AddTextBox`, `AddShape`, `AddPicture`, `AddTable`,
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
355
|
+
### Non-negative dimension validation on every `Add*` command
|
|
356
|
+
|
|
357
|
+
`AddTextBox`, `AddShape`, `AddPicture`, `AddTable`, `SetTransform`,
|
|
358
|
+
`AddOleObject`, `AddLinkedOleObject`, `AddLinkedTable`, `AddChart`,
|
|
359
|
+
`AddChart2016`, and `AddMovie` reject **negative** width/height
|
|
360
|
+
client-side (the server's Zod schema is `z.number().int().nonnegative()`).
|
|
361
|
+
|
|
362
|
+
**Zero is allowed**, matching python-pptx and OOXML — `a:ext` cx/cy are
|
|
363
|
+
`ST_PositiveCoordinate` whose XSD facet is `minInclusive="0"`, so a
|
|
364
|
+
zero-area shape (e.g. a degenerate divider line/connector) is valid and
|
|
365
|
+
python-pptx accepts it without complaint. Earlier SDK versions rejected
|
|
366
|
+
zero too; that was a parity departure that broke the common
|
|
367
|
+
"thin divider" idiom and aborted whole command batches when an agent
|
|
368
|
+
passed `height=0`, so it was reverted. A negative value still raises a
|
|
369
|
+
clear, field-named `ValidationError` locally (and `400` server-side)
|
|
370
|
+
rather than producing an invalid `<a:ext>`.
|
|
371
|
+
|
|
372
|
+
(For `AddPicture`, omit `width=`/`height=` entirely to fall back to the
|
|
373
|
+
image's native dimensions — that path is unchanged.)
|
|
374
|
+
|
|
375
|
+
`SetColWidth` and `SetRowHeight` also allow zero (`min(0)` server-side)
|
|
376
|
+
because zero means "auto-fit" for table dimensions. `SetPresentationSize`
|
|
377
|
+
still requires strictly positive dimensions (a zero-size slide is
|
|
378
|
+
degenerate, not a valid OOXML construct).
|
|
368
379
|
|
|
369
380
|
### Server fail-fast on partial batch failure
|
|
370
381
|
|
|
@@ -735,18 +746,35 @@ Athena-only. Equivalent to calling `prs.set_slide_size(Emu(14630400),
|
|
|
735
746
|
Emu(8229600))` immediately after creation. For non-standard sizes call
|
|
736
747
|
`prs.set_slide_size(width, height)` after `create()`.
|
|
737
748
|
|
|
738
|
-
### `Presentation.asset_id` —
|
|
749
|
+
### `Presentation(asset_id=...)` / `Presentation.asset_id` — preferred spelling; `deck_id` is the legacy alias
|
|
739
750
|
|
|
740
751
|
The deck id and the Athena asset id are the same string
|
|
741
|
-
(`asset_<uuid>`); the
|
|
742
|
-
schemas, and most agent-facing copy refer to "asset id".
|
|
743
|
-
|
|
752
|
+
(`asset_<uuid>`); `asset_id` is the preferred name because Olympus
|
|
753
|
+
URLs, GraphQL schemas, and most agent-facing copy refer to "asset id".
|
|
754
|
+
Upstream `python-pptx` opens a local OPC package via
|
|
755
|
+
`Presentation(pptx_path)` — the REST SDK has no local package, so the
|
|
756
|
+
constructor reattaches to a server-side asset by id instead. Both the
|
|
757
|
+
constructor keyword and the read property exist in both spellings:
|
|
744
758
|
|
|
745
759
|
```python
|
|
746
|
-
prs
|
|
747
|
-
prs
|
|
760
|
+
prs = Presentation(asset_id="asset_3a93...") # preferred
|
|
761
|
+
prs = Presentation(deck_id="asset_3a93...") # legacy alias — same behaviour
|
|
762
|
+
|
|
763
|
+
prs.asset_id # "asset_3a93..." — preferred
|
|
764
|
+
prs.deck_id # same value — legacy alias
|
|
748
765
|
```
|
|
749
766
|
|
|
767
|
+
Rules (mirrors the `name`/`title` reconciliation on
|
|
768
|
+
`Presentation.create()`):
|
|
769
|
+
|
|
770
|
+
- `asset_id` and `deck_id` are interchangeable; internally both bind the
|
|
771
|
+
same identifier (`self._deck_id`).
|
|
772
|
+
- Passing **both** is allowed only when the values match; differing
|
|
773
|
+
values raise `ValueError`.
|
|
774
|
+
- Passing **neither** raises `TypeError` (previously `deck_id` was the
|
|
775
|
+
required first positional parameter; positional
|
|
776
|
+
`Presentation("asset_...")` still works unchanged).
|
|
777
|
+
|
|
750
778
|
### `Presentation.close()` — alias for `flush()`
|
|
751
779
|
|
|
752
780
|
Stock python-pptx has no flush/close concept (it writes packages
|
|
@@ -19,8 +19,9 @@ for real-time collaboration. Use exactly the same code you would with python-ppt
|
|
|
19
19
|
# Or upload an existing file
|
|
20
20
|
prs = Presentation.upload("my_presentation.pptx")
|
|
21
21
|
|
|
22
|
-
# Or connect to an existing
|
|
23
|
-
|
|
22
|
+
# Or connect to an existing presentation asset
|
|
23
|
+
# (deck_id= is accepted as a legacy alias for asset_id=)
|
|
24
|
+
prs = Presentation(asset_id="asset_3a9328bc-9c1c-4498-be8f-bda3883276f5")
|
|
24
25
|
|
|
25
26
|
# Work with slides and shapes (same API as python-pptx)
|
|
26
27
|
slide = prs.slides[0]
|
|
@@ -132,7 +133,7 @@ def flush_all() -> None:
|
|
|
132
133
|
_active_buffers[:] = alive
|
|
133
134
|
|
|
134
135
|
|
|
135
|
-
__version__ = "0.4.
|
|
136
|
+
__version__ = "0.4.2"
|
|
136
137
|
|
|
137
138
|
__all__ = [
|
|
138
139
|
# Main entry point
|
|
@@ -666,6 +666,7 @@ class Client:
|
|
|
666
666
|
flipV=transform_data.get("flipV"),
|
|
667
667
|
),
|
|
668
668
|
preview_text=elem_data.get("previewText"),
|
|
669
|
+
rich_content=elem_data.get("richContent"),
|
|
669
670
|
placeholder=placeholder,
|
|
670
671
|
properties=elem_data.get("properties"),
|
|
671
672
|
source=elem_data.get("source"),
|
|
@@ -87,14 +87,14 @@ class AddTextBox(Command):
|
|
|
87
87
|
def validate(self) -> None:
|
|
88
88
|
if self.slide_index < 0:
|
|
89
89
|
raise ValidationError("slide_index must be non-negative", "slide_index")
|
|
90
|
-
#
|
|
91
|
-
#
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
if self.w_emu
|
|
95
|
-
raise ValidationError(f"width must be
|
|
96
|
-
if self.h_emu
|
|
97
|
-
raise ValidationError(f"height must be
|
|
90
|
+
# OOXML `a:ext` cx/cy are ST_PositiveCoordinate (minInclusive=0), so
|
|
91
|
+
# zero-area shapes are valid (e.g. a degenerate divider/connector) and
|
|
92
|
+
# python-pptx accepts them. Only negatives are rejected, matching the
|
|
93
|
+
# server Zod schema (z.number().int().nonnegative()).
|
|
94
|
+
if self.w_emu < 0:
|
|
95
|
+
raise ValidationError(f"width must be >= 0 EMU (got {self.w_emu})", "w_emu")
|
|
96
|
+
if self.h_emu < 0:
|
|
97
|
+
raise ValidationError(f"height must be >= 0 EMU (got {self.h_emu})", "h_emu")
|
|
98
98
|
|
|
99
99
|
|
|
100
100
|
@dataclass
|
|
@@ -155,14 +155,15 @@ class SetTransform(Command):
|
|
|
155
155
|
def validate(self) -> None:
|
|
156
156
|
if not self.shape_id:
|
|
157
157
|
raise ValidationError("shape_id is required", "shape_id")
|
|
158
|
-
# Server Zod schema: wEmu / hEmu are z.number().int().
|
|
159
|
-
#
|
|
160
|
-
#
|
|
161
|
-
#
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
158
|
+
# Server Zod schema: wEmu / hEmu are z.number().int().nonnegative() —
|
|
159
|
+
# zero is valid (OOXML a:ext cx/cy are ST_PositiveCoordinate,
|
|
160
|
+
# minInclusive=0; python-pptx accepts zero-area shapes). Only negatives
|
|
161
|
+
# are rejected, mirrored locally so they surface synchronously with the
|
|
162
|
+
# offending field rather than failing server-side after the round-trip.
|
|
163
|
+
if self.w_emu is not None and self.w_emu < 0:
|
|
164
|
+
raise ValidationError(f"width must be >= 0 EMU (got {self.w_emu})", "w_emu")
|
|
165
|
+
if self.h_emu is not None and self.h_emu < 0:
|
|
166
|
+
raise ValidationError(f"height must be >= 0 EMU (got {self.h_emu})", "h_emu")
|
|
166
167
|
|
|
167
168
|
|
|
168
169
|
@dataclass
|
|
@@ -293,6 +294,10 @@ class SetParagraphStyle(Command):
|
|
|
293
294
|
space_after_emu: Optional[int] = None
|
|
294
295
|
margin_left_emu: Optional[int] = None
|
|
295
296
|
indent_emu: Optional[int] = None
|
|
297
|
+
# Authored paragraph-level run defaults (python-pptx ``paragraph.font``).
|
|
298
|
+
# Keys are already wire-format camelCase (the Font style payload);
|
|
299
|
+
# the studio exports them as ``<a:pPr><a:defRPr …/></a:pPr>``.
|
|
300
|
+
default_run_style: Optional[dict] = None
|
|
296
301
|
|
|
297
302
|
@property
|
|
298
303
|
def command_type(self) -> str:
|
|
@@ -352,10 +357,10 @@ class AddShape(Command):
|
|
|
352
357
|
raise ValidationError("slide_index must be non-negative", "slide_index")
|
|
353
358
|
if not self.shape_type:
|
|
354
359
|
raise ValidationError("shape_type is required", "shape_type")
|
|
355
|
-
if self.w_emu
|
|
356
|
-
raise ValidationError(f"width must be
|
|
357
|
-
if self.h_emu
|
|
358
|
-
raise ValidationError(f"height must be
|
|
360
|
+
if self.w_emu < 0:
|
|
361
|
+
raise ValidationError(f"width must be >= 0 EMU (got {self.w_emu})", "w_emu")
|
|
362
|
+
if self.h_emu < 0:
|
|
363
|
+
raise ValidationError(f"height must be >= 0 EMU (got {self.h_emu})", "h_emu")
|
|
359
364
|
|
|
360
365
|
|
|
361
366
|
@dataclass
|
|
@@ -492,12 +497,13 @@ class AddPicture(Command):
|
|
|
492
497
|
raise ValidationError(
|
|
493
498
|
"image_format must be 'png', 'jpeg', 'gif', 'bmp', or 'tiff'", "image_format"
|
|
494
499
|
)
|
|
495
|
-
# Server schema treats wEmu / hEmu as
|
|
496
|
-
# them entirely to fall back to the image's native
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
500
|
+
# Server schema treats wEmu / hEmu as nonnegative() when present (zero
|
|
501
|
+
# is allowed; omit them entirely to fall back to the image's native
|
|
502
|
+
# size). Only negatives are rejected.
|
|
503
|
+
if self.w_emu is not None and self.w_emu < 0:
|
|
504
|
+
raise ValidationError(f"width must be >= 0 EMU (got {self.w_emu})", "w_emu")
|
|
505
|
+
if self.h_emu is not None and self.h_emu < 0:
|
|
506
|
+
raise ValidationError(f"height must be >= 0 EMU (got {self.h_emu})", "h_emu")
|
|
501
507
|
|
|
502
508
|
|
|
503
509
|
@dataclass
|
|
@@ -547,10 +553,10 @@ class AddOleObject(Command):
|
|
|
547
553
|
def validate(self) -> None:
|
|
548
554
|
if self.slide_index < 0:
|
|
549
555
|
raise ValidationError("slide_index must be non-negative", "slide_index")
|
|
550
|
-
if self.w_emu
|
|
551
|
-
raise ValidationError(f"width must be
|
|
552
|
-
if self.h_emu
|
|
553
|
-
raise ValidationError(f"height must be
|
|
556
|
+
if self.w_emu < 0:
|
|
557
|
+
raise ValidationError(f"width must be >= 0 EMU (got {self.w_emu})", "w_emu")
|
|
558
|
+
if self.h_emu < 0:
|
|
559
|
+
raise ValidationError(f"height must be >= 0 EMU (got {self.h_emu})", "h_emu")
|
|
554
560
|
if not self.preview_base64:
|
|
555
561
|
raise ValidationError("preview_base64 is required", "preview_base64")
|
|
556
562
|
if self.preview_format not in ("png", "jpeg"):
|
|
@@ -635,10 +641,10 @@ class AddLinkedOleObject(Command):
|
|
|
635
641
|
def validate(self) -> None:
|
|
636
642
|
if self.slide_index < 0:
|
|
637
643
|
raise ValidationError("slide_index must be non-negative", "slide_index")
|
|
638
|
-
if self.w_emu
|
|
639
|
-
raise ValidationError(f"width must be
|
|
640
|
-
if self.h_emu
|
|
641
|
-
raise ValidationError(f"height must be
|
|
644
|
+
if self.w_emu < 0:
|
|
645
|
+
raise ValidationError(f"width must be >= 0 EMU (got {self.w_emu})", "w_emu")
|
|
646
|
+
if self.h_emu < 0:
|
|
647
|
+
raise ValidationError(f"height must be >= 0 EMU (got {self.h_emu})", "h_emu")
|
|
642
648
|
if not isinstance(self.source_ref, dict) or not self.source_ref.get("id"):
|
|
643
649
|
raise ValidationError(
|
|
644
650
|
"source_ref must be an AssetReference dict with an 'id' field",
|
|
@@ -730,10 +736,10 @@ class AddLinkedTable(Command):
|
|
|
730
736
|
def validate(self) -> None:
|
|
731
737
|
if self.slide_index < 0:
|
|
732
738
|
raise ValidationError("slide_index must be non-negative", "slide_index")
|
|
733
|
-
if self.w_emu
|
|
734
|
-
raise ValidationError(f"width must be
|
|
735
|
-
if self.h_emu
|
|
736
|
-
raise ValidationError(f"height must be
|
|
739
|
+
if self.w_emu < 0:
|
|
740
|
+
raise ValidationError(f"width must be >= 0 EMU (got {self.w_emu})", "w_emu")
|
|
741
|
+
if self.h_emu < 0:
|
|
742
|
+
raise ValidationError(f"height must be >= 0 EMU (got {self.h_emu})", "h_emu")
|
|
737
743
|
if not isinstance(self.source_ref, dict) or not self.source_ref.get("id"):
|
|
738
744
|
raise ValidationError(
|
|
739
745
|
"source_ref must be an AssetReference dict with an 'id' field",
|
|
@@ -803,10 +809,10 @@ class AddTable(Command):
|
|
|
803
809
|
raise ValidationError("rows must be between 1 and 100", "rows")
|
|
804
810
|
if self.cols < 1 or self.cols > 26:
|
|
805
811
|
raise ValidationError("cols must be between 1 and 26", "cols")
|
|
806
|
-
if self.w_emu
|
|
807
|
-
raise ValidationError(f"width must be
|
|
808
|
-
if self.h_emu
|
|
809
|
-
raise ValidationError(f"height must be
|
|
812
|
+
if self.w_emu < 0:
|
|
813
|
+
raise ValidationError(f"width must be >= 0 EMU (got {self.w_emu})", "w_emu")
|
|
814
|
+
if self.h_emu < 0:
|
|
815
|
+
raise ValidationError(f"height must be >= 0 EMU (got {self.h_emu})", "h_emu")
|
|
810
816
|
|
|
811
817
|
|
|
812
818
|
@dataclass
|
|
@@ -2260,10 +2266,10 @@ class AddChart(Command):
|
|
|
2260
2266
|
f"chart_type must be one of {sorted(valid_types)}",
|
|
2261
2267
|
"chart_type",
|
|
2262
2268
|
)
|
|
2263
|
-
if self.w_emu
|
|
2264
|
-
raise ValidationError("w_emu must be
|
|
2265
|
-
if self.h_emu
|
|
2266
|
-
raise ValidationError("h_emu must be
|
|
2269
|
+
if self.w_emu < 0:
|
|
2270
|
+
raise ValidationError("w_emu must be >= 0", "w_emu")
|
|
2271
|
+
if self.h_emu < 0:
|
|
2272
|
+
raise ValidationError("h_emu must be >= 0", "h_emu")
|
|
2267
2273
|
if not self.series:
|
|
2268
2274
|
raise ValidationError("at least one series is required", "series")
|
|
2269
2275
|
if self.grouping is not None and self.grouping not in (
|
|
@@ -2576,10 +2582,10 @@ class AddChart2016(Command):
|
|
|
2576
2582
|
f"chart_type must be one of {sorted(valid)} (got {self.chart_type!r})",
|
|
2577
2583
|
"chart_type",
|
|
2578
2584
|
)
|
|
2579
|
-
if self.w_emu
|
|
2580
|
-
raise ValidationError("w_emu must be
|
|
2581
|
-
if self.h_emu
|
|
2582
|
-
raise ValidationError("h_emu must be
|
|
2585
|
+
if self.w_emu < 0:
|
|
2586
|
+
raise ValidationError("w_emu must be >= 0", "w_emu")
|
|
2587
|
+
if self.h_emu < 0:
|
|
2588
|
+
raise ValidationError("h_emu must be >= 0", "h_emu")
|
|
2583
2589
|
if not self.series:
|
|
2584
2590
|
raise ValidationError("at least one series is required", "series")
|
|
2585
2591
|
|
|
@@ -2952,10 +2958,10 @@ class AddMovie(Command):
|
|
|
2952
2958
|
def validate(self) -> None:
|
|
2953
2959
|
if self.slide_index < 0:
|
|
2954
2960
|
raise ValidationError("slide_index must be >= 0", "slide_index")
|
|
2955
|
-
if self.w_emu
|
|
2956
|
-
raise ValidationError("w_emu must be
|
|
2957
|
-
if self.h_emu
|
|
2958
|
-
raise ValidationError("h_emu must be
|
|
2961
|
+
if self.w_emu < 0:
|
|
2962
|
+
raise ValidationError("w_emu must be >= 0", "w_emu")
|
|
2963
|
+
if self.h_emu < 0:
|
|
2964
|
+
raise ValidationError("h_emu must be >= 0", "h_emu")
|
|
2959
2965
|
# Reject empty strings — they look "set" but ship no payload.
|
|
2960
2966
|
has_embedded = bool(self.movie_base64)
|
|
2961
2967
|
has_url = bool(self.url)
|