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.
Files changed (108) hide show
  1. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/API_PARITY_REPORT.md +1 -1
  2. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/CHANGELOG.md +25 -0
  3. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/PKG-INFO +1 -1
  4. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/docs/API_PARITY_EXCEPTIONS.md +35 -0
  5. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/__init__.py +1 -1
  6. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/batching.py +9 -1
  7. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/client.py +42 -0
  8. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/commands.py +25 -0
  9. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/presentation.py +28 -9
  10. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/slides.py +33 -1
  11. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/typing.py +2 -0
  12. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pyproject.toml +1 -1
  13. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/.gitignore +0 -0
  14. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/AGENTS.md +0 -0
  15. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/CLAUDE.md +0 -0
  16. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/DEV-GUIDE.md +0 -0
  17. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/PARITY_QUESTIONS.md +0 -0
  18. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/README.md +0 -0
  19. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/docs/athena-api.json +0 -0
  20. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/docs/athena-api.md +0 -0
  21. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/docs/sdk_usage_census.json +0 -0
  22. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/_athena_extension.py +0 -0
  23. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/_citations.py +0 -0
  24. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/_ptc.py +0 -0
  25. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/_references.py +0 -0
  26. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/_unsupported.py +0 -0
  27. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/action.py +0 -0
  28. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/chart/__init__.py +0 -0
  29. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/chart/axis.py +0 -0
  30. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/chart/category.py +0 -0
  31. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/chart/chart.py +0 -0
  32. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/chart/data.py +0 -0
  33. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/chart/datalabel.py +0 -0
  34. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/chart/legend.py +0 -0
  35. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/chart/marker.py +0 -0
  36. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/chart/plot.py +0 -0
  37. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/chart/point.py +0 -0
  38. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/chart/series.py +0 -0
  39. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/chart/xlsx.py +0 -0
  40. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/decorators.py +0 -0
  41. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/dml/__init__.py +0 -0
  42. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/dml/chtfmt.py +0 -0
  43. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/dml/color.py +0 -0
  44. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/dml/effect.py +0 -0
  45. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/dml/fill.py +0 -0
  46. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/dml/line.py +0 -0
  47. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/docgen.py +0 -0
  48. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/enum/__init__.py +0 -0
  49. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/enum/action.py +0 -0
  50. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/enum/chart.py +0 -0
  51. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/enum/dml.py +0 -0
  52. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/enum/lang.py +0 -0
  53. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/enum/shapes.py +0 -0
  54. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/enum/text.py +0 -0
  55. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/errors.py +0 -0
  56. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/exc.py +0 -0
  57. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/generated/__init__.py +0 -0
  58. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/generated/autoshape_types.py +0 -0
  59. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/kit.py +0 -0
  60. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/media.py +0 -0
  61. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/__init__.py +0 -0
  62. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/_mirror.py +0 -0
  63. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/_tbl.py +0 -0
  64. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/_txbody.py +0 -0
  65. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/ns.py +0 -0
  66. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/presentation.py +0 -0
  67. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/shapes/__init__.py +0 -0
  68. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/shapes/autoshape.py +0 -0
  69. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/shapes/connector.py +0 -0
  70. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/shapes/graphfrm.py +0 -0
  71. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/shapes/groupshape.py +0 -0
  72. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/shapes/picture.py +0 -0
  73. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/shapes/shared.py +0 -0
  74. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/simpletypes.py +0 -0
  75. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/table.py +0 -0
  76. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/text.py +0 -0
  77. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/oxml/xmlchemy.py +0 -0
  78. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/package.py +0 -0
  79. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/parts/__init__.py +0 -0
  80. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/parts/_base.py +0 -0
  81. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/parts/chart.py +0 -0
  82. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/parts/coreprops.py +0 -0
  83. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/parts/embeddedpackage.py +0 -0
  84. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/parts/image.py +0 -0
  85. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/parts/media.py +0 -0
  86. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/parts/presentation.py +0 -0
  87. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/parts/slide.py +0 -0
  88. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/shapes/__init__.py +0 -0
  89. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/shapes/autoshape.py +0 -0
  90. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/shapes/base.py +0 -0
  91. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/shapes/connector.py +0 -0
  92. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/shapes/freeform.py +0 -0
  93. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/shapes/graphfrm.py +0 -0
  94. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/shapes/group.py +0 -0
  95. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/shapes/picture.py +0 -0
  96. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/shapes/placeholder.py +0 -0
  97. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/shapes/shapetree.py +0 -0
  98. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/shared.py +0 -0
  99. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/slide.py +0 -0
  100. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/spec.py +0 -0
  101. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/table.py +0 -0
  102. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/text/__init__.py +0 -0
  103. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/text/fonts.py +0 -0
  104. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/text/layout.py +0 -0
  105. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/text/text.py +0 -0
  106. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/types.py +0 -0
  107. {athena_python_pptx-0.19.0 → athena_python_pptx-0.20.0}/pptx/units.py +0 -0
  108. {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
- | … 17 more | | | | |
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.19.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) |
@@ -327,7 +327,7 @@ def validate_all_touched() -> dict[int, list[str]]:
327
327
  return results
328
328
 
329
329
 
330
- __version__ = "0.19.0"
330
+ __version__ = "0.20.0"
331
331
 
332
332
  __all__ = [
333
333
  # Main entry point
@@ -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._deck_id,
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
- self._deck_id = resolved_id
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._buffer = CommandBuffer(self._client, resolved_id)
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._deck_id
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._deck_id
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.deck_id
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._deck_id,
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._deck_id}' slides={slide_count}>"
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.19.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"