athena-python-pptx 0.15.0__tar.gz → 0.16.1__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.15.0 → athena_python_pptx-0.16.1}/AGENTS.md +1 -1
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/CHANGELOG.md +39 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/DEV-GUIDE.md +7 -8
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/PKG-INFO +1 -1
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/docs/API_PARITY_EXCEPTIONS.md +29 -20
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/docs/athena-api.md +6 -2
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/__init__.py +2 -2
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/_ptc.py +1 -3
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/batching.py +2 -1
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/client.py +13 -11
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/presentation.py +113 -49
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/shapes/__init__.py +2 -2
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/slides.py +62 -6
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pyproject.toml +1 -1
- athena_python_pptx-0.15.0/PUBLISHING.md +0 -177
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/.gitignore +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/API_PARITY_REPORT.md +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/CLAUDE.md +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/PARITY_QUESTIONS.md +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/README.md +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/docs/athena-api.json +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/_athena_extension.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/_citations.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/_references.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/_unsupported.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/action.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/chart/__init__.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/chart/axis.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/chart/category.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/chart/chart.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/chart/data.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/chart/datalabel.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/chart/legend.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/chart/marker.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/chart/plot.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/chart/point.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/chart/series.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/chart/xlsx.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/commands.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/decorators.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/dml/__init__.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/dml/chtfmt.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/dml/color.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/dml/effect.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/dml/fill.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/dml/line.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/docgen.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/enum/__init__.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/enum/action.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/enum/chart.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/enum/dml.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/enum/lang.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/enum/shapes.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/enum/text.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/errors.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/exc.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/generated/__init__.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/generated/autoshape_types.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/kit.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/media.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/oxml/__init__.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/oxml/ns.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/package.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/parts/__init__.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/parts/_base.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/parts/chart.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/parts/coreprops.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/parts/embeddedpackage.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/parts/image.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/parts/media.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/parts/presentation.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/parts/slide.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/shapes/autoshape.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/shapes/base.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/shapes/connector.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/shapes/freeform.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/shapes/graphfrm.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/shapes/group.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/shapes/picture.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/shapes/placeholder.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/shapes/shapetree.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/shared.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/slide.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/spec.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/table.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/text/__init__.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/text/fonts.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/text/layout.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/text/text.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/types.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/typing.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/units.py +0 -0
- {athena_python_pptx-0.15.0 → athena_python_pptx-0.16.1}/pptx/util.py +0 -0
|
@@ -124,7 +124,7 @@ This is a **REST API client** that mimics the python-pptx API. Instead of manipu
|
|
|
124
124
|
- State is synchronized via the PPTX Studio collaboration layer (Yjs)
|
|
125
125
|
- **Programmatic Tool Calling (PTC):** every `CommandBuffer.add` and
|
|
126
126
|
`flush` emits begin/end events to agora via `pptx/_ptc.py` when run
|
|
127
|
-
inside
|
|
127
|
+
inside an Athena sandbox with `ATHENA_PTC_URL` set. Each method call
|
|
128
128
|
surfaces as a nested sub-tool-card under the parent
|
|
129
129
|
`run_python_code` tool. See
|
|
130
130
|
[`docs/PROGRAMMATIC_TOOL_CALLING_GUIDE.md`](../../docs/PROGRAMMATIC_TOOL_CALLING_GUIDE.md)
|
|
@@ -2,6 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `athena-python-pptx` are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.16.1 — `Presentation.upload()` and `create(pptx=...)` report the deck they create
|
|
6
|
+
|
|
7
|
+
- `Presentation.upload()` emits the `CreatePresentation` tool-call event with
|
|
8
|
+
the new deck's id, as `Presentation.create()` and the docx and xlsx uploads
|
|
9
|
+
do. It emitted nothing, so a deck uploaded from sandbox code, including one
|
|
10
|
+
seeded from a brand template with `Presentation.create(pptx=...)`, showed no
|
|
11
|
+
creation card and never joined the Athena session's created assets.
|
|
12
|
+
|
|
13
|
+
## 0.16.0 — `Presentation.create()` makes 13.333" × 7.5" decks; `create(name=, title=)` titles the deck
|
|
14
|
+
|
|
15
|
+
- **Behaviour change:** `Presentation.create()` makes a deck at PowerPoint's
|
|
16
|
+
Widescreen size, 16:9 at 13.333" × 7.5" (12192000 × 6858000 EMU) — the
|
|
17
|
+
size of the blank template, whose master and layouts are laid out for it.
|
|
18
|
+
It used to resize every blank deck to 16" × 9" (14630400 × 8229600 EMU),
|
|
19
|
+
which changed only the size field: layout placeholders sat off-centre, and
|
|
20
|
+
a deck laid out for 13.333" × 7.5" filled only the top-left 83% of each
|
|
21
|
+
slide's width and height, leaving a white band on the right and bottom. `widescreen=True` (the
|
|
22
|
+
default) now sends nothing unless the template has another size.
|
|
23
|
+
`widescreen=False` keeps the template's size, which is the same: it never
|
|
24
|
+
made a 4:3 deck, whatever the docstring said. Decks created earlier keep
|
|
25
|
+
their stored 16" × 9" size.
|
|
26
|
+
- `Presentation.create(name=..., title=...)` with different values names the
|
|
27
|
+
asset `name` and sets the deck's core title (`prs.core_properties.title`)
|
|
28
|
+
to `title`, also with `pptx=`. It used to raise `ValueError`. `title=`
|
|
29
|
+
alone, or the same value for both, still just names the deck.
|
|
30
|
+
|
|
31
|
+
## 0.16.0 — deleting most of an existing deck needs allow_bulk_delete=True
|
|
32
|
+
|
|
33
|
+
- **Behaviour change:** a run that opens an existing deck of 4 or more slides
|
|
34
|
+
and deletes more than half of them — `slides.keep_only([0])`, a
|
|
35
|
+
`delete_slides()` of most indices, or `slides.delete()` calls adding up —
|
|
36
|
+
raises `ValueError` before anything is sent, unless the call passes
|
|
37
|
+
`allow_bulk_delete=True` (a new keyword on `delete()`, `delete_slides()`
|
|
38
|
+
and `keep_only()`). An agent asked to add one slide had re-run its whole
|
|
39
|
+
build (`keep_only([0])` then rebuild), and stopping it midway left a
|
|
40
|
+
21-slide deck half-built. Decks the run created with
|
|
41
|
+
`Presentation.create()` or `Presentation.upload()` are not guarded, so
|
|
42
|
+
trimming a fresh template still works.
|
|
43
|
+
|
|
5
44
|
## 0.15.0 — uploaded click actions read back; pictures carry python-pptx's descr; layout graphics refuse delete()
|
|
6
45
|
|
|
7
46
|
- `shape.click_action` reads the click actions of an uploaded deck as
|
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
## Quick Setup
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
#
|
|
7
|
-
cd python-sdk
|
|
8
|
-
|
|
6
|
+
# Editable install with the dev group, in the SDK's own .venv
|
|
7
|
+
cd pptx-studio/python-sdk
|
|
8
|
+
uv sync
|
|
9
9
|
|
|
10
10
|
# Point at local API
|
|
11
11
|
export ATHENA_PPTX_BASE_URL=http://localhost:4000
|
|
12
12
|
|
|
13
|
-
# Start the API server (in another terminal)
|
|
14
|
-
|
|
13
|
+
# Start the API server (in another terminal, from pptx-studio/)
|
|
14
|
+
bun run dev
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
## Testing Changes
|
|
@@ -38,7 +38,7 @@ with prs.batch():
|
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
python scratch.py
|
|
41
|
+
uv run python scratch.py
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
### Run tests
|
|
@@ -68,8 +68,7 @@ CI installs with pip, so a committed lock would be read by nothing.
|
|
|
68
68
|
### Interactive REPL
|
|
69
69
|
|
|
70
70
|
```bash
|
|
71
|
-
|
|
72
|
-
ATHENA_PPTX_BASE_URL=http://localhost:4000 ipython
|
|
71
|
+
ATHENA_PPTX_BASE_URL=http://localhost:4000 uv run --with ipython ipython
|
|
73
72
|
```
|
|
74
73
|
|
|
75
74
|
### Debug HTTP calls
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: athena-python-pptx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.16.1
|
|
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
|
|
@@ -1192,7 +1192,7 @@ to be world-readable, like any plain image link.
|
|
|
1192
1192
|
download the URL externally and pass `bytes` or a file-like.**
|
|
1193
1193
|
|
|
1194
1194
|
```python
|
|
1195
|
-
# Inside
|
|
1195
|
+
# Inside the presentation-exec sandbox:
|
|
1196
1196
|
img = ws.export_range_as_image_asset("A1:F20", name="Q4 Revenue")
|
|
1197
1197
|
slide.shapes.add_picture(img.url, Inches(1), Inches(1), width=Inches(6))
|
|
1198
1198
|
```
|
|
@@ -1788,29 +1788,37 @@ These additions don't change any python-pptx behaviour — they're new
|
|
|
1788
1788
|
optional surface on top of the existing API. Portable code that needs to
|
|
1789
1789
|
run against stock `python-pptx` should avoid them.
|
|
1790
1790
|
|
|
1791
|
-
### `Presentation.create(title=...)` —
|
|
1791
|
+
### `Presentation.create(title=...)` — names the deck, or titles it next to `name=`
|
|
1792
1792
|
|
|
1793
1793
|
Upstream `python-pptx` has no `Presentation.create()` factory at all;
|
|
1794
|
-
this whole method is an Athena addition.
|
|
1794
|
+
this whole method is an Athena addition. On its own, `title=` is a
|
|
1795
1795
|
convenience alias for `name=` because callers often think of the deck
|
|
1796
|
-
label as a "title" rather than a "name".
|
|
1797
|
-
`
|
|
1796
|
+
label as a "title" rather than a "name". Next to a different `name=`,
|
|
1797
|
+
the asset is named `name` and `title` becomes the deck's core title
|
|
1798
|
+
(`prs.core_properties.title`, the title PowerPoint shows in File > Info):
|
|
1798
1799
|
|
|
1799
1800
|
```python
|
|
1800
1801
|
Presentation.create(name="Q4") # canonical
|
|
1801
1802
|
Presentation.create(title="Q4") # alias — same result
|
|
1802
|
-
Presentation.create(name="Q4", title="Q4") #
|
|
1803
|
-
Presentation.create(name="
|
|
1803
|
+
Presentation.create(name="Q4", title="Q4") # same result
|
|
1804
|
+
Presentation.create(name="q4-review", title="Q4 Review")
|
|
1805
|
+
# asset named "q4-review"; prs.core_properties.title == "Q4 Review"
|
|
1804
1806
|
```
|
|
1805
1807
|
|
|
1806
1808
|
### `Presentation.create(widescreen=True)`
|
|
1807
1809
|
|
|
1808
|
-
Optional kwarg on `Presentation.create()`
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1810
|
+
Optional kwarg on `Presentation.create()`, `True` by default: the deck
|
|
1811
|
+
gets PowerPoint's Widescreen size, 16:9 at 13.333" × 7.5" (12192000 ×
|
|
1812
|
+
6858000 EMU). The blank template new decks start from already has that
|
|
1813
|
+
size and its master and layouts are laid out for it, so nothing is sent
|
|
1814
|
+
unless the template has another size. Athena-only. `widescreen=False`
|
|
1815
|
+
keeps the template's own size, which today is the same 13.333" × 7.5":
|
|
1816
|
+
there is no 4:3 blank template, and resizing a blank deck to 4:3 would
|
|
1817
|
+
leave its master and layout placeholders laid out for 13.333". For any
|
|
1818
|
+
other size call `prs.set_slide_size(width, height)` after `create()`;
|
|
1819
|
+
the master and layouts keep their geometry. Read the size from
|
|
1820
|
+
`prs.slide_width` / `prs.slide_height` (or `prs.slide_size_inches`)
|
|
1821
|
+
rather than assuming it.
|
|
1814
1822
|
|
|
1815
1823
|
### `Presentation(asset_id=...)` / `Presentation.asset_id` — preferred spelling; `deck_id` is the legacy alias
|
|
1816
1824
|
|
|
@@ -1978,8 +1986,8 @@ xlsx-studio's ownership middleware checks that the calling workspace
|
|
|
1978
1986
|
can read the source workbook. Render scale is fixed at 3 (matches
|
|
1979
1987
|
xlsx-studio's `range-as-asset` default — slide inserts sit at 6–9″
|
|
1980
1988
|
widths where scale=2 leaves text soft). When `ATHENA_XLSX_BASE_URL` /
|
|
1981
|
-
`ATHENA_XLSX_API_KEY` aren't set (e.g. running outside
|
|
1982
|
-
|
|
1989
|
+
`ATHENA_XLSX_API_KEY` aren't set (e.g. running outside the
|
|
1990
|
+
presentation-exec sandbox), the call raises `ValueError` rather than
|
|
1983
1991
|
producing a broken picture.
|
|
1984
1992
|
|
|
1985
1993
|
For finer control (custom scale, `include_headers`, off-snapshot
|
|
@@ -2205,11 +2213,12 @@ work in addition to SDK surface and are tracked separately.
|
|
|
2205
2213
|
|
|
2206
2214
|
### Slide- and presentation-level additions
|
|
2207
2215
|
|
|
2208
|
-
- **`Presentation.create(widescreen=True)` is
|
|
2216
|
+
- **`Presentation.create(widescreen=True)` is the default**
|
|
2209
2217
|
(closes python-pptx#1066, 9-comment thread). The blank-template
|
|
2210
|
-
factory creates 16:9
|
|
2211
|
-
|
|
2212
|
-
|
|
2218
|
+
factory creates 16:9 decks at PowerPoint's Widescreen size,
|
|
2219
|
+
13.333" × 7.5" (12192000 × 6858000 EMU) — the size of the blank
|
|
2220
|
+
template itself. `widescreen=False` keeps the template's size, which is
|
|
2221
|
+
the same; there is no 4:3 blank deck.
|
|
2213
2222
|
|
|
2214
2223
|
- **`Presentation.open(deck_id, base_url=None, api_key=None)`**
|
|
2215
2224
|
(closes python-pptx#1018 — "open a PPT in append mode"). Named
|
|
@@ -2624,7 +2633,7 @@ python-pptx. Generated from `pptx._athena_extension.iter_athena_extensions()`.
|
|
|
2624
2633
|
<!-- BEGIN GENERATED: athena-extensions (scripts/generate_parity_tables.py; do not edit by hand) -->
|
|
2625
2634
|
| Surface | Kind | Since | Upstream issue | What it is |
|
|
2626
2635
|
|---|---|---|---|---|
|
|
2627
|
-
| `pptx._ptc` | module | 0.1.0 | | Programmatic Tool Calling (
|
|
2636
|
+
| `pptx._ptc` | module | 0.1.0 | | Programmatic Tool Calling (sandbox event emission). |
|
|
2628
2637
|
| `pptx._references` | module | 0.1.71 | | Cross-studio asset reference types (Athena studio-linking, no upstream). |
|
|
2629
2638
|
| `pptx.action.ActionSetting.action` | property | 0.15.0 | | ActionSetting.action setter: relative slide jumps and end show (read-only in python-pptx). |
|
|
2630
2639
|
| `pptx.action._ActionHyperlink.tooltip` | property | 0.15.0 | | click_action.hyperlink.tooltip: hover text for a shape's click action. |
|
|
@@ -109,17 +109,19 @@ new_slide = slide.clone(target_index=len(prs.slides))
|
|
|
109
109
|
Delete multiple slides from the presentation by their indices.
|
|
110
110
|
|
|
111
111
|
```python
|
|
112
|
-
def delete_slides(indices: list[int]) -> None
|
|
112
|
+
def delete_slides(indices: list[int], *, allow_bulk_delete: bool = False) -> None
|
|
113
113
|
```
|
|
114
114
|
|
|
115
115
|
**Parameters:**
|
|
116
116
|
|
|
117
117
|
- **indices** (`list[int]`): List of zero-based slide indices to delete
|
|
118
|
+
- **allow_bulk_delete** (`bool`): Allow deleting more than half of the slides an existing deck (4+ slides) had when this run opened it.
|
|
118
119
|
|
|
119
120
|
**Raises:**
|
|
120
121
|
|
|
121
122
|
- `ValueError`: If any index is out of range
|
|
122
123
|
- `ValueError`: If duplicate indices are provided
|
|
124
|
+
- `ValueError`: If the run would delete most of an existing deck's slides without `allow_bulk_delete=True`
|
|
123
125
|
|
|
124
126
|
**Example:**
|
|
125
127
|
|
|
@@ -144,18 +146,20 @@ prs.slides.delete_slides([len(prs.slides) - 1, len(prs.slides) - 2])
|
|
|
144
146
|
Keep only the slides at the specified indices, deleting all others.
|
|
145
147
|
|
|
146
148
|
```python
|
|
147
|
-
def keep_only(indices: list[int]) -> None
|
|
149
|
+
def keep_only(indices: list[int], *, allow_bulk_delete: bool = False) -> None
|
|
148
150
|
```
|
|
149
151
|
|
|
150
152
|
**Parameters:**
|
|
151
153
|
|
|
152
154
|
- **indices** (`list[int]`): List of zero-based slide indices to keep
|
|
155
|
+
- **allow_bulk_delete** (`bool`): Allow deleting more than half of the slides an existing deck (4+ slides) had when this run opened it.
|
|
153
156
|
|
|
154
157
|
**Raises:**
|
|
155
158
|
|
|
156
159
|
- `ValueError`: If any index is out of range
|
|
157
160
|
- `ValueError`: If duplicate indices are provided
|
|
158
161
|
- `ValueError`: If indices list is empty
|
|
162
|
+
- `ValueError`: If the run would delete most of an existing deck's slides without `allow_bulk_delete=True`
|
|
159
163
|
|
|
160
164
|
**Example:**
|
|
161
165
|
|
|
@@ -189,7 +189,7 @@ def _flush_failure_message(buffer: "CommandBuffer", exc: BaseException) -> str:
|
|
|
189
189
|
def flush_all(*, strict: bool = False) -> None:
|
|
190
190
|
"""Flush all active presentation command buffers.
|
|
191
191
|
|
|
192
|
-
Called by
|
|
192
|
+
Called by Athena's sandbox executor after user code runs. By default a
|
|
193
193
|
per-buffer flush failure is written to stderr and swallowed (local-script
|
|
194
194
|
back-compat); the sandbox executor calls ``flush_all(strict=True)`` so a
|
|
195
195
|
server rejection of the final buffered edits surfaces as a real tool
|
|
@@ -327,7 +327,7 @@ def validate_all_touched() -> dict[int, list[str]]:
|
|
|
327
327
|
return results
|
|
328
328
|
|
|
329
329
|
|
|
330
|
-
__version__ = "0.
|
|
330
|
+
__version__ = "0.16.1"
|
|
331
331
|
|
|
332
332
|
__all__ = [
|
|
333
333
|
# Main entry point
|
|
@@ -37,9 +37,7 @@ of stdlib code is cheaper than spinning up a 4th release pipeline.
|
|
|
37
37
|
from __future__ import annotations
|
|
38
38
|
|
|
39
39
|
__athena_extension_module__: bool = True
|
|
40
|
-
__athena_extension_description__: str = (
|
|
41
|
-
"Programmatic Tool Calling (Daytona sandbox event emission)."
|
|
42
|
-
)
|
|
40
|
+
__athena_extension_description__: str = "Programmatic Tool Calling (sandbox event emission)."
|
|
43
41
|
__athena_extension_since__: str = "0.1.0"
|
|
44
42
|
|
|
45
43
|
import http.client
|
|
@@ -656,7 +656,8 @@ class CommandBuffer:
|
|
|
656
656
|
|
|
657
657
|
An auto-flush timer (default 2.0s) automatically flushes pending commands
|
|
658
658
|
after an idle period. This ensures commands are not silently lost in
|
|
659
|
-
persistent process environments (e.g.
|
|
659
|
+
persistent process environments (e.g. long-lived sandbox executors) where
|
|
660
|
+
atexit never fires.
|
|
660
661
|
|
|
661
662
|
Usage:
|
|
662
663
|
# Default: commands are buffered
|
|
@@ -138,6 +138,17 @@ def _poll_delays(poll_interval: float, max_attempts: int) -> Iterator[float]:
|
|
|
138
138
|
delay = min(delay * 1.5, poll_interval)
|
|
139
139
|
|
|
140
140
|
|
|
141
|
+
def _upload_deck_name(*, file_path: str, name: Optional[str]) -> str:
|
|
142
|
+
"""The name an upload gives its deck: ``name``, else the file's name
|
|
143
|
+
without a ``.pptx`` / ``.potx`` extension."""
|
|
144
|
+
if name is not None:
|
|
145
|
+
return name
|
|
146
|
+
base = os.path.basename(file_path)
|
|
147
|
+
if base.endswith(".pptx") or base.endswith(".potx"):
|
|
148
|
+
return base[:-5]
|
|
149
|
+
return base
|
|
150
|
+
|
|
151
|
+
|
|
141
152
|
def _is_read_timeout(exc: BaseException) -> bool:
|
|
142
153
|
"""A response that did not arrive in time. With retries configured,
|
|
143
154
|
requests reports a read timeout as a ConnectionError wrapping urllib3's
|
|
@@ -926,12 +937,7 @@ class Client:
|
|
|
926
937
|
if not os.path.exists(file_path):
|
|
927
938
|
raise FileNotFoundError(f"File not found: {file_path}")
|
|
928
939
|
|
|
929
|
-
|
|
930
|
-
if name is None:
|
|
931
|
-
name = os.path.basename(file_path)
|
|
932
|
-
# Remove extension
|
|
933
|
-
if name.endswith(".pptx") or name.endswith(".potx"):
|
|
934
|
-
name = name[:-5]
|
|
940
|
+
name = _upload_deck_name(file_path=file_path, name=name)
|
|
935
941
|
|
|
936
942
|
# Step 1: Create deck to get presigned URL
|
|
937
943
|
deck_info = self.create_deck(name)
|
|
@@ -1007,11 +1013,7 @@ class Client:
|
|
|
1007
1013
|
if not os.path.exists(file_path):
|
|
1008
1014
|
raise FileNotFoundError(f"File not found: {file_path}")
|
|
1009
1015
|
|
|
1010
|
-
|
|
1011
|
-
if name is None:
|
|
1012
|
-
name = os.path.basename(file_path)
|
|
1013
|
-
if name.endswith(".pptx") or name.endswith(".potx"):
|
|
1014
|
-
name = name[:-5]
|
|
1016
|
+
name = _upload_deck_name(file_path=file_path, name=name)
|
|
1015
1017
|
|
|
1016
1018
|
# Step 1: Create deck to get presigned URL
|
|
1017
1019
|
deck_info = self.create_deck(name)
|
|
@@ -17,7 +17,7 @@ import warnings
|
|
|
17
17
|
|
|
18
18
|
from ._athena_extension import athena_extension
|
|
19
19
|
from .batching import CommandBuffer, batch_context
|
|
20
|
-
from .client import Client, _hand_session_to_next_client
|
|
20
|
+
from .client import Client, _hand_session_to_next_client, _upload_deck_name
|
|
21
21
|
from .commands import SetCoreProperties, SetPresentationSize, CopySlideFromDeck
|
|
22
22
|
from .errors import AuthenticationError, RemoteError, UnsupportedFeatureError
|
|
23
23
|
from .slides import SlideLayouts, SlideMasters, Slides
|
|
@@ -33,6 +33,11 @@ from .units import Emu, Length, ensure_emu
|
|
|
33
33
|
# asset and capture its id from ``prs.deck_id``.
|
|
34
34
|
_DECK_ID_PATTERN = re.compile(r"^(asset_|deck_)[A-Za-z0-9_-]+$")
|
|
35
35
|
|
|
36
|
+
# PowerPoint's "Widescreen" slide size (Design > Slide Size), 16:9 at
|
|
37
|
+
# 13.333" x 7.5" — the size of the blank template new decks start from, whose
|
|
38
|
+
# master and layouts are laid out for it.
|
|
39
|
+
_WIDESCREEN_SLIDE_SIZE_EMU = (12192000, 6858000)
|
|
40
|
+
|
|
36
41
|
|
|
37
42
|
# -------------------------------------------------------------------------
|
|
38
43
|
# CoreProperties — python-pptx's ``prs.core_properties``
|
|
@@ -273,6 +278,14 @@ def _open_failure(*, target: str, exc: Exception) -> Exception:
|
|
|
273
278
|
return exc
|
|
274
279
|
|
|
275
280
|
|
|
281
|
+
def _release_opened_slides(prs: Any) -> None:
|
|
282
|
+
"""A deck this run created or uploaded has no earlier slides to protect."""
|
|
283
|
+
prs._protect_slides_pending = False
|
|
284
|
+
slides = getattr(prs, "_slides", None)
|
|
285
|
+
if slides is not None:
|
|
286
|
+
slides._release_opened_slides()
|
|
287
|
+
|
|
288
|
+
|
|
276
289
|
class Presentation:
|
|
277
290
|
"""
|
|
278
291
|
Presentation proxy - the main entry point for working with a deck.
|
|
@@ -406,6 +419,10 @@ class Presentation:
|
|
|
406
419
|
self._atexit_flush = _flush_on_exit
|
|
407
420
|
atexit.register(_flush_on_exit)
|
|
408
421
|
|
|
422
|
+
# The first snapshot adopted (here or a later manual refresh())
|
|
423
|
+
# records the slides the bulk-delete guard protects.
|
|
424
|
+
self._protect_slides_pending = True
|
|
425
|
+
|
|
409
426
|
# PTC: surface deck open as its own sub-tool-card so the
|
|
410
427
|
# frontend can react (e.g., auto-open the asset tab). The
|
|
411
428
|
# paired ``Presentation.create()`` path emits
|
|
@@ -683,9 +700,11 @@ class Presentation:
|
|
|
683
700
|
"""
|
|
684
701
|
Create a new presentation.
|
|
685
702
|
|
|
686
|
-
By default this creates an empty
|
|
687
|
-
that's immediately ready to
|
|
688
|
-
content.
|
|
703
|
+
By default this creates an empty presentation at PowerPoint's
|
|
704
|
+
Widescreen size (16:9, 13.333" x 7.5") that's immediately ready to
|
|
705
|
+
use; you can then add slides and content. Read the size from
|
|
706
|
+
``prs.slide_width`` / ``prs.slide_height`` (or
|
|
707
|
+
``prs.slide_size_inches``) rather than assuming it.
|
|
689
708
|
|
|
690
709
|
When ``pptx`` is provided, the local ``.pptx`` / ``.potx`` file is
|
|
691
710
|
uploaded as the seed instead of starting from a blank template.
|
|
@@ -706,22 +725,27 @@ class Presentation:
|
|
|
706
725
|
file. When provided, the file is uploaded and used as the
|
|
707
726
|
seed for the new presentation (mirrors python-pptx's
|
|
708
727
|
``Presentation(pptx=...)`` constructor kwarg).
|
|
709
|
-
title: Optional
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
728
|
+
title: Optional deck title. On its own it names the deck, as an
|
|
729
|
+
alias for ``name`` (callers often think of the deck label
|
|
730
|
+
as a "title"). With a different ``name``, the deck is
|
|
731
|
+
named ``name`` and ``title`` becomes its core title
|
|
732
|
+
(``prs.core_properties.title``, the title PowerPoint
|
|
733
|
+
shows in File > Info). The same value for both just
|
|
734
|
+
names the deck.
|
|
735
|
+
widescreen: Give the deck PowerPoint's Widescreen size: 16:9,
|
|
736
|
+
13.333" x 7.5" (12192000 x 6858000 EMU). The blank
|
|
737
|
+
template already has that size, so nothing is sent
|
|
738
|
+
unless the template has another one. Defaults to
|
|
739
|
+
``True``. ``widescreen=False`` keeps the template's
|
|
740
|
+
own size, which today is the same 13.333" x 7.5":
|
|
741
|
+
there is no 4:3 blank template, and a 4:3 resize
|
|
742
|
+
would leave the master and layout placeholders laid
|
|
743
|
+
out for 13.333". For another size call
|
|
744
|
+
:meth:`set_slide_size` after ``create()``; the master
|
|
745
|
+
and layouts keep their geometry. Ignored when
|
|
746
|
+
``pptx=`` is provided (the uploaded file defines its
|
|
747
|
+
own slide size). Not in upstream python-pptx, which
|
|
748
|
+
has no factory method here at all.
|
|
725
749
|
|
|
726
750
|
Returns:
|
|
727
751
|
Presentation instance for the new deck
|
|
@@ -741,25 +765,25 @@ class Presentation:
|
|
|
741
765
|
)
|
|
742
766
|
slide = prs.slides.add_slide()
|
|
743
767
|
|
|
744
|
-
#
|
|
745
|
-
prs = Presentation.create(name="
|
|
768
|
+
# Asset named "q3-review", deck titled "Q3 Review"
|
|
769
|
+
prs = Presentation.create(name="q3-review", title="Q3 Review")
|
|
770
|
+
print(prs.slide_size_inches) # (13.333..., 7.5)
|
|
746
771
|
"""
|
|
747
|
-
#
|
|
748
|
-
#
|
|
749
|
-
#
|
|
772
|
+
# ``title`` alone names the deck. Next to a different ``name`` it is
|
|
773
|
+
# the deck's core title, checked here so a title python-pptx would
|
|
774
|
+
# refuse fails before any deck exists.
|
|
775
|
+
core_title: str | None = None
|
|
750
776
|
if title is not None and name is not None and title != name:
|
|
751
|
-
|
|
752
|
-
"Presentation.create(name=..., title=...) — name and title "
|
|
753
|
-
"must match when both are provided. Pass only one."
|
|
754
|
-
)
|
|
777
|
+
core_title = _core_text_value(title)
|
|
755
778
|
effective_name = name if name is not None else title
|
|
756
779
|
|
|
757
780
|
from pptx import _ptc
|
|
758
781
|
|
|
759
782
|
if pptx is not None:
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
783
|
+
uploaded = cls.upload(pptx, base_url=base_url, api_key=api_key, name=effective_name)
|
|
784
|
+
if core_title is not None:
|
|
785
|
+
uploaded.core_properties.title = core_title
|
|
786
|
+
return uploaded
|
|
763
787
|
|
|
764
788
|
# PTC: surface asset creation as its own top-level sub-tool-card.
|
|
765
789
|
# Scoped tightly around ``create_empty_deck`` — the deck is
|
|
@@ -791,12 +815,16 @@ class Presentation:
|
|
|
791
815
|
# The presentation's client takes over this connection.
|
|
792
816
|
with _hand_session_to_next_client(client):
|
|
793
817
|
prs = cls(deck_id=deck_id, base_url=base_url, api_key=api_key)
|
|
818
|
+
_release_opened_slides(prs)
|
|
794
819
|
|
|
795
|
-
# Apply widescreen sizing before slide cleanup so the size command
|
|
796
|
-
# is sent even if the template arrives with no default slides.
|
|
797
820
|
if widescreen:
|
|
798
|
-
|
|
799
|
-
prs.
|
|
821
|
+
width_emu, height_emu = _WIDESCREEN_SLIDE_SIZE_EMU
|
|
822
|
+
snapshot = prs._snapshot
|
|
823
|
+
if snapshot is None or (
|
|
824
|
+
int(snapshot.slide_width_emu),
|
|
825
|
+
int(snapshot.slide_height_emu),
|
|
826
|
+
) != (width_emu, height_emu):
|
|
827
|
+
prs.set_slide_size(Emu(width_emu), Emu(height_emu))
|
|
800
828
|
|
|
801
829
|
# Delete any default slides from the blank template
|
|
802
830
|
# The blank template includes one slide that we need to remove
|
|
@@ -804,6 +832,9 @@ class Presentation:
|
|
|
804
832
|
while len(prs.slides) > 0:
|
|
805
833
|
prs.slides.delete(prs.slides[0])
|
|
806
834
|
|
|
835
|
+
if core_title is not None:
|
|
836
|
+
prs.core_properties.title = core_title
|
|
837
|
+
|
|
807
838
|
return prs
|
|
808
839
|
|
|
809
840
|
@classmethod
|
|
@@ -863,25 +894,55 @@ class Presentation:
|
|
|
863
894
|
# Save changes
|
|
864
895
|
prs.save("modified.pptx")
|
|
865
896
|
"""
|
|
897
|
+
from pptx import _ptc
|
|
898
|
+
|
|
866
899
|
client = Client(base_url=base_url, api_key=api_key, prefer_user_key=True)
|
|
867
900
|
|
|
868
901
|
# The presentation's client takes over this connection (closed here
|
|
869
902
|
# when the upload or the open fails first).
|
|
870
903
|
with _hand_session_to_next_client(client):
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
904
|
+
# PTC: the upload creates a deck asset, so it wears the same card
|
|
905
|
+
# as ``create()`` and the docx/xlsx uploads. Agora adds a deck to
|
|
906
|
+
# the session's created assets only from this event. Scoped to the
|
|
907
|
+
# upload as in ``create()``: the open below emits its own
|
|
908
|
+
# ``OpenPresentation``.
|
|
909
|
+
call_id = _ptc.emit_begin(
|
|
910
|
+
"CreatePresentation",
|
|
911
|
+
{"name": _upload_deck_name(file_path=str(path), name=name)},
|
|
912
|
+
)
|
|
913
|
+
try:
|
|
914
|
+
if wait:
|
|
915
|
+
kwargs: dict[str, Any] = {}
|
|
916
|
+
if max_wait_seconds is not None:
|
|
917
|
+
# Match the client's poll_interval default (0.5s) so callers
|
|
918
|
+
# can think in seconds without remembering attempt math.
|
|
919
|
+
from .client import DEFAULT_POLL_INTERVAL
|
|
920
|
+
|
|
921
|
+
kwargs["max_attempts"] = max(
|
|
922
|
+
1, int(max_wait_seconds / DEFAULT_POLL_INTERVAL)
|
|
923
|
+
)
|
|
924
|
+
deck_id = client.upload_file(str(path), name=name, **kwargs)
|
|
925
|
+
else:
|
|
926
|
+
# Upload without waiting
|
|
927
|
+
deck_id = client.upload_file_async(str(path), name=name)
|
|
928
|
+
except Exception as exc:
|
|
929
|
+
_ptc.emit_end(
|
|
930
|
+
call_id=call_id,
|
|
931
|
+
tool_name="CreatePresentation",
|
|
932
|
+
result={"ok": False, "error": str(exc)},
|
|
933
|
+
is_error=True,
|
|
934
|
+
)
|
|
935
|
+
raise
|
|
936
|
+
_ptc.emit_end(
|
|
937
|
+
call_id=call_id,
|
|
938
|
+
tool_name="CreatePresentation",
|
|
939
|
+
result={"ok": True, "assetId": deck_id},
|
|
940
|
+
is_error=False,
|
|
941
|
+
)
|
|
883
942
|
|
|
884
|
-
|
|
943
|
+
prs = cls(deck_id=deck_id, base_url=base_url, api_key=api_key, auto_refresh=wait)
|
|
944
|
+
_release_opened_slides(prs)
|
|
945
|
+
return prs
|
|
885
946
|
|
|
886
947
|
@property
|
|
887
948
|
@athena_extension(
|
|
@@ -1932,6 +1993,9 @@ class Presentation:
|
|
|
1932
1993
|
buffer=self._buffer,
|
|
1933
1994
|
snapshot=snapshot,
|
|
1934
1995
|
)
|
|
1996
|
+
if self.__dict__.get("_protect_slides_pending"):
|
|
1997
|
+
self._protect_slides_pending = False
|
|
1998
|
+
self._slides._protect_opened_slides()
|
|
1935
1999
|
|
|
1936
2000
|
# A rejected shape graph leaves the previously published snapshot and
|
|
1937
2001
|
# held slide proxies intact, including reconciliation aliases for retry.
|
|
@@ -92,7 +92,7 @@ _ATHENA_URL_SUFFIXES: tuple[str, ...] = (
|
|
|
92
92
|
)
|
|
93
93
|
|
|
94
94
|
# Hard cap on bytes read for a URL-supplied image. Olympus's asset proxy
|
|
95
|
-
# limits in practice, but ``add_picture`` runs inside a
|
|
95
|
+
# limits in practice, but ``add_picture`` runs inside a sandbox
|
|
96
96
|
# with finite memory, so we want a deterministic local stop regardless
|
|
97
97
|
# of what the server claims.
|
|
98
98
|
_MAX_URL_IMAGE_BYTES = 50 * 1024 * 1024
|
|
@@ -292,7 +292,7 @@ def _try_convert_to_png(data: bytes, image_format: str) -> Optional[bytes]:
|
|
|
292
292
|
produces a broken-image placeholder on the slide.
|
|
293
293
|
|
|
294
294
|
WebP → PNG: requires ``Pillow`` (transitively included via
|
|
295
|
-
``matplotlib`` in the
|
|
295
|
+
``matplotlib`` in the presentation-exec image, so this
|
|
296
296
|
just works in the standard agent runtime).
|
|
297
297
|
|
|
298
298
|
AVIF → PNG: same Pillow path, but only on Pillow builds with AVIF
|