athena-python-pptx 0.19.0__tar.gz → 0.20.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.
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/API_PARITY_REPORT.md +1 -1
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/CHANGELOG.md +25 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/PKG-INFO +1 -1
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/docs/API_PARITY_EXCEPTIONS.md +35 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/__init__.py +1 -1
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/batching.py +9 -1
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/client.py +42 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/commands.py +25 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/presentation.py +28 -9
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/slides.py +33 -1
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/typing.py +2 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pyproject.toml +1 -1
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/.gitignore +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/AGENTS.md +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/CLAUDE.md +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/DEV-GUIDE.md +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/PARITY_QUESTIONS.md +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/README.md +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/docs/athena-api.json +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/docs/athena-api.md +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/docs/sdk_usage_census.json +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/_athena_extension.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/_citations.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/_ptc.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/_references.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/_unsupported.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/action.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/chart/__init__.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/chart/axis.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/chart/category.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/chart/chart.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/chart/data.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/chart/datalabel.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/chart/legend.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/chart/marker.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/chart/plot.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/chart/point.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/chart/series.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/chart/xlsx.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/decorators.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/dml/__init__.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/dml/chtfmt.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/dml/color.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/dml/effect.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/dml/fill.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/dml/line.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/docgen.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/enum/__init__.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/enum/action.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/enum/chart.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/enum/dml.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/enum/lang.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/enum/shapes.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/enum/text.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/errors.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/exc.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/generated/__init__.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/generated/autoshape_types.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/kit.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/media.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/__init__.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/_mirror.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/_tbl.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/_txbody.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/ns.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/presentation.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/shapes/__init__.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/shapes/autoshape.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/shapes/connector.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/shapes/graphfrm.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/shapes/groupshape.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/shapes/picture.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/shapes/shared.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/simpletypes.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/table.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/text.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/xmlchemy.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/package.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/parts/__init__.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/parts/_base.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/parts/chart.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/parts/coreprops.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/parts/embeddedpackage.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/parts/image.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/parts/media.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/parts/presentation.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/parts/slide.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/shapes/__init__.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/shapes/autoshape.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/shapes/base.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/shapes/connector.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/shapes/freeform.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/shapes/graphfrm.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/shapes/group.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/shapes/picture.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/shapes/placeholder.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/shapes/shapetree.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/shared.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/slide.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/spec.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/table.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/text/__init__.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/text/fonts.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/text/layout.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/text/text.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/types.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/units.py +0 -0
- {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/util.py +0 -0
|
@@ -90,7 +90,7 @@ at an API, which the skill text and error messages can steer.
|
|
|
90
90
|
| `Shape.margin_top` | set | <3 | <3 | <3 |
|
|
91
91
|
| `Shape.vertical_anchor` | set | <3 | <3 | <3 |
|
|
92
92
|
| `Shapes.clear_shapes` | call | <3 | <3 | <3 |
|
|
93
|
-
| …
|
|
93
|
+
| … 15 more | | | | |
|
|
94
94
|
|
|
95
95
|
Agent code also reached names neither the SDK nor python-pptx has (attributes it hung on SDK
|
|
96
96
|
objects, made-up enum members) on `Client`, `MSO_SHAPE`, `Paragraph`, `Shape`, `SlideLayout`, `Slides`, `_ChartSeries`. The census
|
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `athena-python-pptx` are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.20.0 — an older deck opens by its deck id
|
|
6
|
+
|
|
7
|
+
- `Presentation(asset_id=...)` asks pptx-studio once, at open, for the id it
|
|
8
|
+
names the deck by (`GET /decks/by-asset/<asset id>/deck-id`, #46493). An older deck's
|
|
9
|
+
room, and every `/decks/...` route, name it by its deck id rather than its
|
|
10
|
+
asset id (#46319). Every snapshot, command, export and render then uses that
|
|
11
|
+
id, so code run against an older deck edits the deck instead of an empty
|
|
12
|
+
room.
|
|
13
|
+
- `prs.asset_id` (and its `deck_id` alias) stays the id you passed, and tool
|
|
14
|
+
cards still name the asset.
|
|
15
|
+
- Only a successful answer for this asset is used. Any other outcome keeps the
|
|
16
|
+
asset id, as every open did before, with one `pptx.client` log record: a
|
|
17
|
+
studio without the route, a refusal, an error or a timeout.
|
|
18
|
+
- `copy_slide_from(source)` names an older source deck by its deck id too.
|
|
19
|
+
- Needs a pptx-studio with `GET /decks/by-asset/:assetId/deck-id` (#46590) and agora's
|
|
20
|
+
lookup (#46588); against an older studio, nothing changes.
|
|
21
|
+
|
|
22
|
+
## 0.20.0 — slides hide from the slide show, as PowerPoint's Hide Slide does
|
|
23
|
+
|
|
24
|
+
- `slide.hidden = True` hides a slide from the slide show and keeps it in the
|
|
25
|
+
deck; the saved deck writes `<p:sld show="0">`. `slide.hidden = False` shows
|
|
26
|
+
it again. `slide.hidden` reads an uploaded slide's flag. Not in python-pptx:
|
|
27
|
+
`docs/API_PARITY_EXCEPTIONS.md`, "`Slide.hidden`".
|
|
28
|
+
- Needs a studio with `SetSlideHidden`; an older one rejects the command.
|
|
29
|
+
|
|
5
30
|
## 0.19.0 — `Shapes.add_linked_chart()`: a chart whose data is a source range
|
|
6
31
|
|
|
7
32
|
- `slide.shapes.add_linked_chart(chart_type, source, anchor, left=, top=,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: athena-python-pptx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.20.0
|
|
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
|
|
@@ -1415,6 +1415,18 @@ These edits apply to classic charts (authored or uploaded); Office 2016
|
|
|
1415
1415
|
chartEx charts, such as waterfall and treemap, refuse them
|
|
1416
1416
|
(`chartExEditsRefusal`).
|
|
1417
1417
|
|
|
1418
|
+
### `Slide.hidden` — PowerPoint's Hide Slide (Unreleased)
|
|
1419
|
+
|
|
1420
|
+
python-pptx has no slide-level hidden flag. `slide.hidden = True` hides the
|
|
1421
|
+
slide from the slide show (`<p:sld show="0">` in the saved deck) and keeps it
|
|
1422
|
+
in the deck; `slide.hidden = False` shows it again. `slide.hidden` reads an
|
|
1423
|
+
uploaded slide's own flag, including the rare `<p:sldId show="0">` form. Sends
|
|
1424
|
+
`SetSlideHidden`; a studio that predates it rejects the command.
|
|
1425
|
+
|
|
1426
|
+
```python
|
|
1427
|
+
prs.slides[2].hidden = True
|
|
1428
|
+
```
|
|
1429
|
+
|
|
1418
1430
|
### `Font.cap` — PowerPoint-native capitalization mode
|
|
1419
1431
|
|
|
1420
1432
|
The SDK exposes PowerPoint's OOXML run capitalization property as
|
|
@@ -2631,6 +2643,28 @@ per-element "Auto" option (off by default) re-read the range.
|
|
|
2631
2643
|
`freshness_mode='office_live_link'` is not supported and raises `ValueError`
|
|
2632
2644
|
locally.
|
|
2633
2645
|
|
|
2646
|
+
**From a semantic model:** pass the model's `AssetReference` and a
|
|
2647
|
+
`SemanticQueryAnchor` (from `athena_references`). The studio runs the query
|
|
2648
|
+
as the person the deck is written for, so the chart only shows numbers they
|
|
2649
|
+
may read: the first dimension (or the time dimension) is the categories and
|
|
2650
|
+
each measure a series. A refresh re-runs the query.
|
|
2651
|
+
|
|
2652
|
+
```python
|
|
2653
|
+
from athena_references import SemanticQueryAnchor
|
|
2654
|
+
|
|
2655
|
+
slide.shapes.add_linked_chart(
|
|
2656
|
+
XL_CHART_TYPE.COLUMN_CLUSTERED,
|
|
2657
|
+
AssetReference(id='asset_semantic_model_abc'),
|
|
2658
|
+
SemanticQueryAnchor(query={
|
|
2659
|
+
'measures': ['orders.revenue'],
|
|
2660
|
+
'dimensions': ['orders.region'],
|
|
2661
|
+
'limit': 20,
|
|
2662
|
+
}),
|
|
2663
|
+
left=Inches(1), top=Inches(1), width=Inches(6), height=Inches(4),
|
|
2664
|
+
)
|
|
2665
|
+
prs.flush() # surfaces a refused query here, not at exit
|
|
2666
|
+
```
|
|
2667
|
+
|
|
2634
2668
|
---
|
|
2635
2669
|
|
|
2636
2670
|
## Athena extensions for most-requested upstream features (v0.1.81)
|
|
@@ -3316,6 +3350,7 @@ python-pptx. Generated from `pptx._athena_extension.iter_athena_extensions()`.
|
|
|
3316
3350
|
| `pptx.slides.Slide.apply_foreign_layout` | method | 0.1.82 | | Slide.apply_foreign_layout — apply a layout from a different slide master. |
|
|
3317
3351
|
| `pptx.slides.Slide.clear_shapes` | method | 0.1.78 | | Delete every slide-local shape in one batched call |
|
|
3318
3352
|
| `pptx.slides.Slide.clone` | method | 0.1.6 | | Clone slide with all its content |
|
|
3353
|
+
| `pptx.slides.Slide.hidden` | property | Unreleased | | Slide.hidden — PowerPoint's Hide Slide (p:sld@show). Not in python-pptx. |
|
|
3319
3354
|
| `pptx.slides.Slide.remove_citation` | method | 0.1.0 | | Slide.remove_citation — remove a citation from the deck by id. Not in python-pptx. |
|
|
3320
3355
|
| `pptx.slides.Slide.render` | method | 0.1.5 | | Render slide to PNG image |
|
|
3321
3356
|
| `pptx.slides.Slide.validate` | method | 0.1.78 | | Validate slide layout (text overflow, hidden text, collisions, off-slide shapes) |
|
|
@@ -150,6 +150,7 @@ _NON_LAYOUT_COMMANDS: frozenset[str] = frozenset(
|
|
|
150
150
|
{
|
|
151
151
|
"SetSlideNotes",
|
|
152
152
|
"SetSlideName",
|
|
153
|
+
"SetSlideHidden",
|
|
153
154
|
"SetSlideBackground",
|
|
154
155
|
"AddSlideCitation",
|
|
155
156
|
"AddShapeCitation",
|
|
@@ -204,6 +205,7 @@ _LOCALLY_MIRRORED_COMMANDS: frozenset[str] = frozenset(
|
|
|
204
205
|
"SetClickAction",
|
|
205
206
|
"SetRunHyperlinkTarget",
|
|
206
207
|
"SetSlideName",
|
|
208
|
+
"SetSlideHidden",
|
|
207
209
|
"SetSlideNotes",
|
|
208
210
|
"SetSlideBackground",
|
|
209
211
|
"SetCoreProperties",
|
|
@@ -714,6 +716,8 @@ class CommandBuffer:
|
|
|
714
716
|
auto_batch: bool = True,
|
|
715
717
|
auto_flush_seconds: Optional[float] = 2.0,
|
|
716
718
|
defer_commits: Optional[bool] = None,
|
|
719
|
+
*,
|
|
720
|
+
asset_id: Optional[str] = None,
|
|
717
721
|
):
|
|
718
722
|
"""
|
|
719
723
|
Initialize the command buffer.
|
|
@@ -730,9 +734,13 @@ class CommandBuffer:
|
|
|
730
734
|
defer_commits: Hold every buffered command until an explicit
|
|
731
735
|
flush (see the class docstring). ``None`` (default)
|
|
732
736
|
follows :func:`executor_mode_enabled`.
|
|
737
|
+
asset_id: The presentation's Athena asset id, which its tool
|
|
738
|
+
cards name. Defaults to ``deck_id``; differs only for
|
|
739
|
+
an older deck, whose pptx-studio id is its deck id.
|
|
733
740
|
"""
|
|
734
741
|
self._client = client
|
|
735
742
|
self._deck_id = deck_id
|
|
743
|
+
self._asset_id = asset_id or deck_id
|
|
736
744
|
self._commands: list[AnyCommand] = []
|
|
737
745
|
self._batch_depth = 0
|
|
738
746
|
self._batch_start_index = 0
|
|
@@ -1141,7 +1149,7 @@ class CommandBuffer:
|
|
|
1141
1149
|
command._ptc_call_id = _ptc.emit_begin( # type: ignore[attr-defined]
|
|
1142
1150
|
type(command).__name__,
|
|
1143
1151
|
command.to_dict(),
|
|
1144
|
-
asset_id=self.
|
|
1152
|
+
asset_id=self._asset_id,
|
|
1145
1153
|
)
|
|
1146
1154
|
except Exception: # noqa: BLE001
|
|
1147
1155
|
pass
|
|
@@ -14,6 +14,7 @@ import contextvars
|
|
|
14
14
|
import hashlib
|
|
15
15
|
import inspect
|
|
16
16
|
import json as _json
|
|
17
|
+
import logging
|
|
17
18
|
import os
|
|
18
19
|
import re
|
|
19
20
|
import threading
|
|
@@ -39,6 +40,8 @@ from .errors import (
|
|
|
39
40
|
RenderError,
|
|
40
41
|
UploadError,
|
|
41
42
|
)
|
|
43
|
+
|
|
44
|
+
logger = logging.getLogger(__name__)
|
|
42
45
|
from .typing import (
|
|
43
46
|
CommandsResponse,
|
|
44
47
|
DeckSnapshot,
|
|
@@ -1171,6 +1174,44 @@ class Client:
|
|
|
1171
1174
|
attribution = os.environ.get("ATHENA_PPTX_CUSTOM_ATTRIBUTIONS") or ""
|
|
1172
1175
|
return (self.base_url, deck_id, identity, attribution)
|
|
1173
1176
|
|
|
1177
|
+
def resolve_deck_id(self, asset_id: str) -> str:
|
|
1178
|
+
"""The id pptx-studio names the deck of ``asset_id`` by, once per open.
|
|
1179
|
+
|
|
1180
|
+
An older deck's room, and every ``/decks/...`` route, name it by its
|
|
1181
|
+
deck id, not its asset id (#46319, #46493). pptx-studio answers it at
|
|
1182
|
+
``GET /decks/by-asset/<asset id>/deck-id`` for a caller who may read the deck.
|
|
1183
|
+
Only a successful answer for this asset is used; any failure (a studio
|
|
1184
|
+
without the route, a refusal, an error, a timeout) keeps the asset id,
|
|
1185
|
+
as every open did before, with one log line. The deck's own calls then
|
|
1186
|
+
surface a real refusal as they always have, and an older deck named by
|
|
1187
|
+
its asset id is refused loudly by pptx-studio rather than written to
|
|
1188
|
+
the wrong room.
|
|
1189
|
+
"""
|
|
1190
|
+
if not asset_id.startswith("asset_"):
|
|
1191
|
+
return asset_id
|
|
1192
|
+
path = f"/decks/by-asset/{asset_id}/deck-id"
|
|
1193
|
+
try:
|
|
1194
|
+
response = self._send("GET", path)
|
|
1195
|
+
if response.status_code == 200:
|
|
1196
|
+
data = response.json()
|
|
1197
|
+
deck_id = data.get("deck_id") if isinstance(data, dict) else None
|
|
1198
|
+
if (
|
|
1199
|
+
isinstance(deck_id, str)
|
|
1200
|
+
and deck_id
|
|
1201
|
+
and data.get("asset_id") == asset_id
|
|
1202
|
+
):
|
|
1203
|
+
return deck_id
|
|
1204
|
+
reason = f"HTTP {response.status_code}"
|
|
1205
|
+
except Exception as exc: # noqa: BLE001 - never break an open over the lookup
|
|
1206
|
+
reason = type(exc).__name__
|
|
1207
|
+
logger.info(
|
|
1208
|
+
"pptx: no deck id for %s from %s (%s); addressing it by its asset id",
|
|
1209
|
+
asset_id,
|
|
1210
|
+
path,
|
|
1211
|
+
reason,
|
|
1212
|
+
)
|
|
1213
|
+
return asset_id
|
|
1214
|
+
|
|
1174
1215
|
def get_snapshot(self, deck_id: str) -> DeckSnapshot:
|
|
1175
1216
|
"""Get the current state snapshot of a deck.
|
|
1176
1217
|
|
|
@@ -1297,6 +1338,7 @@ class Client:
|
|
|
1297
1338
|
inherited_element_ids=slide_data.get("inheritedElementIds", []),
|
|
1298
1339
|
name=slide_data.get("name") if isinstance(slide_data.get("name"), str) else None,
|
|
1299
1340
|
background_image_asset_id=slide_data.get("backgroundImageAssetId"),
|
|
1341
|
+
hidden=slide_data.get("isHidden") is True,
|
|
1300
1342
|
)
|
|
1301
1343
|
)
|
|
1302
1344
|
|
|
@@ -2371,6 +2371,30 @@ class SetSlideName(Command):
|
|
|
2371
2371
|
raise ValidationError("name must be a string", "name")
|
|
2372
2372
|
|
|
2373
2373
|
|
|
2374
|
+
|
|
2375
|
+
@dataclass
|
|
2376
|
+
class SetSlideHidden(Command):
|
|
2377
|
+
"""
|
|
2378
|
+
Hide a slide from the slide show, or show it again (``p:sld@show``).
|
|
2379
|
+
|
|
2380
|
+
Args:
|
|
2381
|
+
slide_index: Zero-based index of the slide
|
|
2382
|
+
hidden: True hides the slide; False shows it again
|
|
2383
|
+
"""
|
|
2384
|
+
|
|
2385
|
+
slide_index: int
|
|
2386
|
+
hidden: bool
|
|
2387
|
+
|
|
2388
|
+
@property
|
|
2389
|
+
def command_type(self) -> str:
|
|
2390
|
+
return "SetSlideHidden"
|
|
2391
|
+
|
|
2392
|
+
def validate(self) -> None:
|
|
2393
|
+
if self.slide_index < 0:
|
|
2394
|
+
raise ValidationError("slide_index must be non-negative", "slide_index")
|
|
2395
|
+
if not isinstance(self.hidden, bool):
|
|
2396
|
+
raise ValidationError("hidden must be a bool", "hidden")
|
|
2397
|
+
|
|
2374
2398
|
@dataclass
|
|
2375
2399
|
class SetClickAction(Command):
|
|
2376
2400
|
"""
|
|
@@ -3818,6 +3842,7 @@ AnyCommand = Union[
|
|
|
3818
3842
|
SetCoreProperties,
|
|
3819
3843
|
SetShapeName,
|
|
3820
3844
|
SetSlideName,
|
|
3845
|
+
SetSlideHidden,
|
|
3821
3846
|
SetClickAction,
|
|
3822
3847
|
SetPresentationSize,
|
|
3823
3848
|
# Phase 5
|
|
@@ -287,6 +287,18 @@ def _release_opened_slides(prs: Any) -> None:
|
|
|
287
287
|
slides._release_opened_slides()
|
|
288
288
|
|
|
289
289
|
|
|
290
|
+
|
|
291
|
+
def _pptx_deck_id(client: Any, asset_id: str) -> str:
|
|
292
|
+
"""The id pptx-studio names the deck of ``asset_id`` by (#46493).
|
|
293
|
+
|
|
294
|
+
Only an ``asset_`` id is looked up (a legacy ``deck_`` id is the studio's
|
|
295
|
+
own); anything but a non-empty answer keeps the id the caller passed.
|
|
296
|
+
"""
|
|
297
|
+
if not asset_id.startswith("asset_"):
|
|
298
|
+
return asset_id
|
|
299
|
+
resolved = client.resolve_deck_id(asset_id)
|
|
300
|
+
return resolved if isinstance(resolved, str) and resolved else asset_id
|
|
301
|
+
|
|
290
302
|
class Presentation:
|
|
291
303
|
"""
|
|
292
304
|
Presentation proxy - the main entry point for working with a deck.
|
|
@@ -376,9 +388,14 @@ class Presentation:
|
|
|
376
388
|
f"Presentation.create(name={resolved_id!r}) and reuse "
|
|
377
389
|
f"`prs.deck_id` on subsequent calls in the same conversation."
|
|
378
390
|
)
|
|
379
|
-
|
|
391
|
+
# ``asset_id`` (and its ``deck_id`` alias) stays the id the caller
|
|
392
|
+
# passed. pptx-studio names an older deck by its deck id instead
|
|
393
|
+
# (#46319): every ``/decks/...`` call uses ``_deck_id``, resolved once
|
|
394
|
+
# here, and the asset id wherever no lookup answers it (#46493).
|
|
395
|
+
self._asset_id = resolved_id
|
|
380
396
|
self._client = Client(base_url=base_url, api_key=api_key)
|
|
381
|
-
self.
|
|
397
|
+
self._deck_id = _pptx_deck_id(self._client, resolved_id)
|
|
398
|
+
self._buffer = CommandBuffer(self._client, self._deck_id, asset_id=resolved_id)
|
|
382
399
|
self._buffer.set_discard_listener(self._on_commands_discarded)
|
|
383
400
|
self._snapshot: Optional[DeckSnapshot] = None
|
|
384
401
|
self._slides: Optional[Slides] = None
|
|
@@ -953,8 +970,8 @@ class Presentation:
|
|
|
953
970
|
description="Presentation.deck_id — Athena asset id.",
|
|
954
971
|
)
|
|
955
972
|
def deck_id(self) -> DeckId:
|
|
956
|
-
"""ID of the deck
|
|
957
|
-
return self.
|
|
973
|
+
"""ID of the deck: its Athena asset id, as :attr:`asset_id`."""
|
|
974
|
+
return self._asset_id
|
|
958
975
|
|
|
959
976
|
@property
|
|
960
977
|
@athena_extension(
|
|
@@ -971,7 +988,7 @@ class Presentation:
|
|
|
971
988
|
keyword. Athena-only ergonomic addition — not in upstream
|
|
972
989
|
python-pptx.
|
|
973
990
|
"""
|
|
974
|
-
return self.
|
|
991
|
+
return self._asset_id
|
|
975
992
|
|
|
976
993
|
def _on_commands_discarded(self, created_ids: set[str]) -> None:
|
|
977
994
|
"""Commands were dropped unsent (a failed run's ``discard_all``, a
|
|
@@ -1196,11 +1213,13 @@ class Presentation:
|
|
|
1196
1213
|
prs_out.copy_slide_from(prs_in, source_slide_index=5)
|
|
1197
1214
|
prs_out.save()
|
|
1198
1215
|
"""
|
|
1216
|
+
# pptx-studio names the source by its own pptx-studio id: an older
|
|
1217
|
+
# deck's deck id, not its asset id (#46493).
|
|
1199
1218
|
if isinstance(source, str):
|
|
1200
|
-
source_deck_id = source
|
|
1219
|
+
source_deck_id = _pptx_deck_id(self._client, source)
|
|
1201
1220
|
else:
|
|
1202
1221
|
source.flush() # land any pending edits in the source.
|
|
1203
|
-
source_deck_id = source.
|
|
1222
|
+
source_deck_id = source._deck_id
|
|
1204
1223
|
if source_slide_index < 0:
|
|
1205
1224
|
raise ValueError("source_slide_index must be >= 0")
|
|
1206
1225
|
if destination_index < -1:
|
|
@@ -1834,7 +1853,7 @@ class Presentation:
|
|
|
1834
1853
|
Dictionary with presentation metadata and content
|
|
1835
1854
|
"""
|
|
1836
1855
|
return {
|
|
1837
|
-
"deck_id": self.
|
|
1856
|
+
"deck_id": self._asset_id,
|
|
1838
1857
|
"name": self.name,
|
|
1839
1858
|
"slide_count": len(self.slides),
|
|
1840
1859
|
"slide_width_emu": int(self.slide_width),
|
|
@@ -2233,4 +2252,4 @@ class Presentation:
|
|
|
2233
2252
|
|
|
2234
2253
|
def __repr__(self) -> str:
|
|
2235
2254
|
slide_count = len(self._slides) if self._slides else "?"
|
|
2236
|
-
return f"<Presentation deck_id='{self.
|
|
2255
|
+
return f"<Presentation deck_id='{self._asset_id}' slides={slide_count}>"
|
|
@@ -18,7 +18,7 @@ if TYPE_CHECKING:
|
|
|
18
18
|
from .enum.dml import MSO_FILL_TYPE
|
|
19
19
|
from .shapes import Shape
|
|
20
20
|
|
|
21
|
-
from .commands import AddSlide, DeleteSlide, SetSlideBackground, SetSlideNotes, CloneSlide, SetSlideName, SetSlideLayout, MeasureTextFit
|
|
21
|
+
from .commands import AddSlide, DeleteSlide, SetSlideBackground, SetSlideNotes, CloneSlide, SetSlideName, SetSlideHidden, SetSlideLayout, MeasureTextFit
|
|
22
22
|
from .decorators import athena_only
|
|
23
23
|
from .errors import ConflictError, UnsupportedFeatureError, ValidationError
|
|
24
24
|
from .oxml.presentation import CT_SlideIdList
|
|
@@ -2354,6 +2354,7 @@ class Slide:
|
|
|
2354
2354
|
layout_path: Optional[str] = None,
|
|
2355
2355
|
name: Optional[str] = None,
|
|
2356
2356
|
background_image_asset_id: Optional[str] = None,
|
|
2357
|
+
hidden: bool = False,
|
|
2357
2358
|
):
|
|
2358
2359
|
self._presentation = presentation
|
|
2359
2360
|
self._slide_id = slide_id
|
|
@@ -2364,6 +2365,7 @@ class Slide:
|
|
|
2364
2365
|
self._background_image_asset_id = background_image_asset_id
|
|
2365
2366
|
self._notes = notes
|
|
2366
2367
|
self._name = name or ""
|
|
2368
|
+
self._hidden = hidden
|
|
2367
2369
|
|
|
2368
2370
|
# Layout tracking
|
|
2369
2371
|
self._layout_index = layout_index
|
|
@@ -3225,6 +3227,34 @@ class Slide:
|
|
|
3225
3227
|
cmd = SetSlideName(slide_index=self._slide_index, name=self._name)
|
|
3226
3228
|
self._buffer.add(cmd)
|
|
3227
3229
|
|
|
3230
|
+
@property
|
|
3231
|
+
@athena_only(
|
|
3232
|
+
description="Slide.hidden — PowerPoint's Hide Slide (p:sld@show). Not in python-pptx.",
|
|
3233
|
+
since="Unreleased",
|
|
3234
|
+
)
|
|
3235
|
+
def hidden(self) -> bool:
|
|
3236
|
+
"""True when the slide is hidden from the slide show (``p:sld@show="0"``).
|
|
3237
|
+
|
|
3238
|
+
A hidden slide stays in the deck; PowerPoint skips it when presenting.
|
|
3239
|
+
|
|
3240
|
+
Example:
|
|
3241
|
+
slide.hidden = True
|
|
3242
|
+
|
|
3243
|
+
Note (Athena extension):
|
|
3244
|
+
This property is NOT part of python-pptx. See
|
|
3245
|
+
``docs/API_PARITY_EXCEPTIONS.md`` for the deviation rationale.
|
|
3246
|
+
"""
|
|
3247
|
+
return self._hidden
|
|
3248
|
+
|
|
3249
|
+
@hidden.setter
|
|
3250
|
+
def hidden(self, value: bool) -> None:
|
|
3251
|
+
"""Hide the slide from the slide show, or show it again."""
|
|
3252
|
+
if not isinstance(value, bool):
|
|
3253
|
+
raise ValidationError("hidden must be a bool", "hidden")
|
|
3254
|
+
self._hidden = value
|
|
3255
|
+
if self._buffer:
|
|
3256
|
+
self._buffer.add(SetSlideHidden(slide_index=self._slide_index, hidden=value))
|
|
3257
|
+
|
|
3228
3258
|
# -------------------------------------------------------------------------
|
|
3229
3259
|
# Convenience properties and methods
|
|
3230
3260
|
# -------------------------------------------------------------------------
|
|
@@ -3694,6 +3724,7 @@ class Slide:
|
|
|
3694
3724
|
layout_path=slide_snapshot.layout_path,
|
|
3695
3725
|
name=slide_snapshot.name,
|
|
3696
3726
|
background_image_asset_id=slide_snapshot.background_image_asset_id,
|
|
3727
|
+
hidden=slide_snapshot.hidden,
|
|
3697
3728
|
)
|
|
3698
3729
|
|
|
3699
3730
|
def _adopt_prepared_snapshot(self, *, prepared: Slide) -> None:
|
|
@@ -3716,6 +3747,7 @@ class Slide:
|
|
|
3716
3747
|
self._background = None
|
|
3717
3748
|
self._notes = prepared._notes
|
|
3718
3749
|
self._name = prepared._name
|
|
3750
|
+
self._hidden = prepared._hidden
|
|
3719
3751
|
self._layout_index = prepared._layout_index
|
|
3720
3752
|
self._layout_name = prepared._layout_name
|
|
3721
3753
|
self._layout_path = prepared._layout_path
|
|
@@ -178,6 +178,8 @@ class SlideSnapshot:
|
|
|
178
178
|
name: Optional[str] = None
|
|
179
179
|
# The slide's own ``<p:bg>`` picture fill (python-pptx: not a shape).
|
|
180
180
|
background_image_asset_id: Optional[str] = None
|
|
181
|
+
# PowerPoint's Hide Slide (``p:sld@show="0"``); False when the server predates it.
|
|
182
|
+
hidden: bool = False
|
|
181
183
|
|
|
182
184
|
|
|
183
185
|
@dataclass
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "athena-python-pptx"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.20.0"
|
|
8
8
|
description = "Drop-in replacement for python-pptx that connects to PPTX Studio for real-time collaboration"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|