athena-python-pptx 0.18.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 (110) hide show
  1. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/AGENTS.md +35 -7
  2. athena_python_pptx-0.20.0/API_PARITY_REPORT.md +220 -0
  3. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/CHANGELOG.md +217 -0
  4. athena_python_pptx-0.20.0/PARITY_QUESTIONS.md +112 -0
  5. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/PKG-INFO +10 -1
  6. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/README.md +9 -0
  7. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/docs/API_PARITY_EXCEPTIONS.md +284 -60
  8. athena_python_pptx-0.20.0/docs/sdk_usage_census.json +15382 -0
  9. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/__init__.py +1 -1
  10. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/_unsupported.py +1 -5
  11. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/batching.py +20 -1
  12. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/client.py +42 -0
  13. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/commands.py +175 -0
  14. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/enum/lang.py +204 -41
  15. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/oxml/simpletypes.py +3 -3
  16. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/presentation.py +31 -12
  17. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/shapes/__init__.py +1718 -286
  18. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/slides.py +33 -1
  19. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/text/__init__.py +165 -16
  20. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/typing.py +6 -0
  21. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pyproject.toml +1 -1
  22. athena_python_pptx-0.18.0/API_PARITY_REPORT.md +0 -165
  23. athena_python_pptx-0.18.0/PARITY_QUESTIONS.md +0 -304
  24. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/.gitignore +0 -0
  25. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/CLAUDE.md +0 -0
  26. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/DEV-GUIDE.md +0 -0
  27. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/docs/athena-api.json +0 -0
  28. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/docs/athena-api.md +0 -0
  29. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/_athena_extension.py +0 -0
  30. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/_citations.py +0 -0
  31. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/_ptc.py +0 -0
  32. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/_references.py +0 -0
  33. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/action.py +0 -0
  34. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/chart/__init__.py +0 -0
  35. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/chart/axis.py +0 -0
  36. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/chart/category.py +0 -0
  37. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/chart/chart.py +0 -0
  38. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/chart/data.py +0 -0
  39. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/chart/datalabel.py +0 -0
  40. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/chart/legend.py +0 -0
  41. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/chart/marker.py +0 -0
  42. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/chart/plot.py +0 -0
  43. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/chart/point.py +0 -0
  44. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/chart/series.py +0 -0
  45. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/chart/xlsx.py +0 -0
  46. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/decorators.py +0 -0
  47. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/dml/__init__.py +0 -0
  48. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/dml/chtfmt.py +0 -0
  49. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/dml/color.py +0 -0
  50. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/dml/effect.py +0 -0
  51. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/dml/fill.py +0 -0
  52. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/dml/line.py +0 -0
  53. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/docgen.py +0 -0
  54. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/enum/__init__.py +0 -0
  55. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/enum/action.py +0 -0
  56. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/enum/chart.py +0 -0
  57. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/enum/dml.py +0 -0
  58. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/enum/shapes.py +0 -0
  59. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/enum/text.py +0 -0
  60. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/errors.py +0 -0
  61. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/exc.py +0 -0
  62. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/generated/__init__.py +0 -0
  63. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/generated/autoshape_types.py +0 -0
  64. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/kit.py +0 -0
  65. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/media.py +0 -0
  66. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/oxml/__init__.py +0 -0
  67. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/oxml/_mirror.py +0 -0
  68. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/oxml/_tbl.py +0 -0
  69. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/oxml/_txbody.py +0 -0
  70. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/oxml/ns.py +0 -0
  71. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/oxml/presentation.py +0 -0
  72. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/oxml/shapes/__init__.py +0 -0
  73. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/oxml/shapes/autoshape.py +0 -0
  74. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/oxml/shapes/connector.py +0 -0
  75. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/oxml/shapes/graphfrm.py +0 -0
  76. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/oxml/shapes/groupshape.py +0 -0
  77. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/oxml/shapes/picture.py +0 -0
  78. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/oxml/shapes/shared.py +0 -0
  79. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/oxml/table.py +0 -0
  80. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/oxml/text.py +0 -0
  81. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/oxml/xmlchemy.py +0 -0
  82. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/package.py +0 -0
  83. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/parts/__init__.py +0 -0
  84. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/parts/_base.py +0 -0
  85. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/parts/chart.py +0 -0
  86. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/parts/coreprops.py +0 -0
  87. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/parts/embeddedpackage.py +0 -0
  88. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/parts/image.py +0 -0
  89. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/parts/media.py +0 -0
  90. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/parts/presentation.py +0 -0
  91. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/parts/slide.py +0 -0
  92. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/shapes/autoshape.py +0 -0
  93. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/shapes/base.py +0 -0
  94. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/shapes/connector.py +0 -0
  95. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/shapes/freeform.py +0 -0
  96. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/shapes/graphfrm.py +0 -0
  97. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/shapes/group.py +0 -0
  98. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/shapes/picture.py +0 -0
  99. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/shapes/placeholder.py +0 -0
  100. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/shapes/shapetree.py +0 -0
  101. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/shared.py +0 -0
  102. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/slide.py +0 -0
  103. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/spec.py +0 -0
  104. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/table.py +0 -0
  105. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/text/fonts.py +0 -0
  106. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/text/layout.py +0 -0
  107. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/text/text.py +0 -0
  108. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/types.py +0 -0
  109. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/units.py +0 -0
  110. {athena_python_pptx-0.18.0 → athena_python_pptx-0.20.0}/pptx/util.py +0 -0
@@ -23,7 +23,17 @@ Before adding or modifying any API surface:
23
23
  3. Confirm the method/property/parameter exists with the same name and signature
24
24
  4. If it doesn't exist in python-pptx, **do not add it** without explicit user approval
25
25
 
26
- ### Current status (v0.1.74)
26
+ ### Current status
27
+
28
+ [`API_PARITY_REPORT.md`](API_PARITY_REPORT.md) is generated
29
+ (`scripts/generate_parity_report.py`; `tests/test_parity_report_generated.py`
30
+ fails CI when it is stale): it joins the usage census of agent code
31
+ (`docs/sdk_usage_census.json`, built by `scripts/pull_agent_traces.py` and
32
+ `scripts/sdk_usage_census.py`) with the surface audit and python-pptx's
33
+ surface, and ranks the gaps by usage, used and broken first.
34
+ [`PARITY_QUESTIONS.md`](PARITY_QUESTIONS.md) holds the open questions in that
35
+ order. After an SDK change that moves a member, regenerate both; refresh the
36
+ census (a new trace window) with the steps in `scripts/sdk_usage_census.py`.
27
37
 
28
38
  **Full public-class parity reached** against python-pptx 1.0.2 across
29
39
  three dimensions:
@@ -59,11 +69,27 @@ A small number of REST-SDK-specific departures are documented in
59
69
  - **`_BaseSeriesData.index`** raises `NotImplementedError` (raising
60
70
  rather than silently returning 0).
61
71
  - **`Package.open()`** raises `UnsupportedFeatureError`.
62
- - **Axis-title text frames** are one paragraph with one font: runs join
63
- into one title string in the first run's font, with a one-time
64
- `UserWarning` when that drops styling.
65
- - **Table-cell text frames** are one paragraph with one run (bold, italic,
66
- size, name, color); `font.underline` raises.
72
+ - **Axis-title text frames** have one font: their paragraphs and line
73
+ breaks are python-pptx's, but runs join into one title string in the
74
+ first run's font, with a one-time `UserWarning` when that drops styling,
75
+ and there is no `add_paragraph()`.
76
+ - **Chart and axis titles in lines** are written and read as python-pptx's
77
+ (a paragraph per `\n`, a line break per `\v`); a run's own text with
78
+ either character becomes a break in the deck (`docs/API_PARITY_EXCEPTIONS.md`).
79
+ - **Table-cell text frames** hold python-pptx's paragraphs and runs (sent as
80
+ `SetTableCell.paragraphs` once a cell holds more than one run); an empty
81
+ cell's paragraph still lists one empty run, a one-run cell's `paragraph.font`
82
+ is its run's font, and every underline style reads back as `True`
83
+ (`docs/API_PARITY_EXCEPTIONS.md`).
84
+
85
+ ### Approved Athena extensions
86
+
87
+ Members python-pptx lacks that were added with explicit approval, each
88
+ documented in [`docs/API_PARITY_EXCEPTIONS.md`](docs/API_PARITY_EXCEPTIONS.md)
89
+ and listed in `tests/test_python_pptx_api_parity.py`'s allowlist:
90
+ `Shapes.add_linked_ole_object()`, `Shapes.add_linked_table()` and
91
+ `Shapes.add_linked_chart()` (approved 2026-10-20), the studio-linking
92
+ methods that bind an element to an Athena source.
67
93
 
68
94
  ### Intentionally omitted (REST SDK limitations)
69
95
 
@@ -73,7 +99,9 @@ local XML / OPC parts:
73
99
  shape's element, for python-pptx's shape-tree recipes; see Architecture.)
74
100
  - `TextFrame.part`, `Paragraph.part`, `Run.part` — package part access.
75
101
  - `FillFormat.from_fill_parent()`, `ColorFormat.from_colorchoice_parent()` — internal XML constructors.
76
- - `Font.fill` — Font fill format (uncommon; use `font.color` for text color).
102
+
103
+ `Font.fill` exists: `solid()` and `fore_color` drive `font.color`; gradient,
104
+ pattern and no-fill text raise `UnsupportedFeatureError`.
77
105
 
78
106
  `Font.language_id` IS now implemented (returns `MSO_LANGUAGE_ID` enum, accepts enum or int LCID) — added in v0.1.68.
79
107
 
@@ -0,0 +1,220 @@
1
+ # API parity report: athena-python-pptx and python-pptx 1.0.2
2
+
3
+ <!-- Generated by scripts/generate_parity_report.py; do not edit by hand. tests/test_parity_report_generated.py fails CI when it is stale. -->
4
+
5
+ Which python-pptx members agent code calls, what the SDK does with each, and the gaps
6
+ ranked by that usage: used and broken first. Three inputs are joined per member:
7
+
8
+ 1. **What agents call** — `docs/sdk_usage_census.json`, built by `scripts/sdk_usage_census.py`
9
+ from the Python agents sent to Athena's code tools in production traces: static analysis
10
+ (the code is parsed, never run) types each expression against the SDK's classes and records
11
+ the member it reaches.
12
+ 2. **What the SDK does** — the surface audit (`tests/_surface_audit.py`) probes every public
13
+ setter and method on a mocked deck: it sends a command, warns once, raises, or sends nothing;
14
+ `pptx/_unsupported.py` lists members the SDK refuses.
15
+ 3. **What python-pptx has** — python-pptx 1.0.2's public members (the parity tests'
16
+ reference).
17
+
18
+ Counts are customer **workspaces | sessions | tool calls** in the window; Athena staff traffic is
19
+ counted apart and excluded. A count from fewer than three workspaces reads `<3`.
20
+
21
+ ## Census
22
+
23
+ - Source: LangSmith agora-production traces; the 60 days from 2026-08-16 to 2026-10-14 (55 of them with SDK code).
24
+ - Customers: 6335 tool calls with SDK code, 290 sessions, 10 workspaces; computer_asset_exec <3; eval 109; execute <3; execute_presentation_code 5992; run_python_code 121; write_file 26.
25
+ - Athena staff: 1335 tool calls, 101 sessions, 5 workspaces. Left out of both: the calls with neither a signed-in user nor a workspace (no workspace is behind them, so their count is withheld).
26
+ - Members used by customers: 743. How the analysis typed them: downcast 17, heuristic 7, static 660, unknown 59 (`unknown`: no SDK class defines the member).
27
+
28
+ ## Used and broken, ranked by usage
29
+
30
+ Members customers' agents called whose call the SDK does not carry out as python-pptx does today.
31
+ Ranked by customer workspaces, then sessions.
32
+
33
+ | # | python-pptx member | SDK class | op | Workspaces | Sessions | Tool calls | What the SDK does |
34
+ |---|---|---|---|---|---|---|---|
35
+ | 1 | `Shapes._spTree` | `Shapes` | get | <3 | <3 | <3 | raises: in 4 of 8 probed contexts; athena-python-pptx mirrors a slide's own shape tree, slide.shapes._spTree; a notes slide's, layout's or master's is not mirrored. |
36
+ | 2 | `Chart._chartSpace` | `Chart` | get | <3 | <3 | <3 | raises: python-pptx XML internal: Use the Chart API: chart.plots, chart.series, chart.replace_data(chart_data), chart.has_title / chart.chart_title, chart.value_axis, chart.category_axis. |
37
+ | 3 | `Slide._element` | `Slide` | get | <3 | <3 | <3 | raises: python-pptx XML internal: Use the object's own properties and methods. |
38
+ | 4 | `Subshape.part` | `Paragraph` | get | <3 | <3 | <3 | raises: athena-python-pptx edits a deck that lives on the PPTX Studio server, so there are no OPC package parts or relationships to reach. |
39
+ | 5 | `_ChartSeries._element` | `_ChartSeries` | get | <3 | <3 | <3 | raises: python-pptx XML internal: Use the object's own properties and methods. |
40
+ | 6 | `ColorFormat.brightness` | `_FillColorFormat` | set | <3 | <3 | <3 | raises: Shape fills carry no brightness in the Athena studio (SetShapeStyle has no lumMod / lumOff). |
41
+
42
+ ## Known structural gaps, measured
43
+
44
+ The gaps the fidelity plan names (`../docs/plans/2026-10-14-powerpoint-fidelity-program.md` §5.3),
45
+ with the members that reach each. A gap no customer reached in the window is not prioritized.
46
+
47
+ | Gap | Members | Workspaces | Sessions | What the SDK does |
48
+ |---|---|---|---|---|
49
+ | A table cell holds several paragraphs and runs | `_TableCellParagraph.add_run`, `_TableCellTextFrame.add_paragraph` | 3 | 23 | Closed: add_run() / add_paragraph() add a run / paragraph, each run keeps its own font, and the cell's text body is sent run by run (SetTableCell.paragraphs) |
50
+ | Table-cell paragraph spacing and level | `_TableCellParagraph.line_spacing`, `_TableCellParagraph.space_before`, `_TableCellParagraph.space_after`, `_TableCellParagraph.level` | <3 | <3 | Closed: sent with the cell's paragraphs (SetTableCell.paragraphs) |
51
+ | Table-cell word wrap and auto-size | `_TableCellTextFrame.word_wrap`, `_TableCellTextFrame.auto_size` | 3 | 19 | word_wrap = True, the value every measured use sets, is how a cell lays out its text and is accepted; False, None and auto_size warn once and keep the value locally |
52
+ | Table-cell colours are sRGB only; no table-cell hyperlink writes (no customer use) | `_TableCellFontColorFormat.theme_color`, `_TableCellFillColorFormat.theme_color`, `_TableCellHyperlink.address` | 0 | 0 | raise UnsupportedFeatureError |
53
+ | 3-D, stock and surface charts (no customer use) | `XL_CHART_TYPE.THREE_D_*`, `XL_CHART_TYPE.STOCK_*`, `XL_CHART_TYPE.SURFACE*` | 0 | 0 | add_chart() raises UnsupportedFeatureError |
54
+ | Gradient and pattern text (Font.fill) (no customer use) | `_FontFillFormat.gradient`, `_FontFillFormat.patterned` | 0 | 0 | raise UnsupportedFeatureError |
55
+ | Notes-slide shapes and placeholders (no customer use) | `NotesSlide.shapes`, `NotesSlide.placeholders`, `NotesSlide.notes_placeholder` | 0 | 0 | empty collections; notes text works through notes_slide.notes_text_frame |
56
+ | Layout and master backgrounds are read-only (no customer use) | `_LayoutBackgroundFill.solid`, `_LayoutBackgroundColor.rgb` | 0 | 0 | raise UnsupportedFeatureError |
57
+
58
+ Workspaces and sessions are the largest count of any one member of the gap (a lower bound on the
59
+ gap's reach).
60
+
61
+ ## Members agents call that python-pptx does not have
62
+
63
+ Not parity gaps: python-pptx raises `AttributeError` for these too. They show where agents guess
64
+ at an API, which the skill text and error messages can steer.
65
+
66
+ | Member | op | Workspaces | Sessions | Tool calls |
67
+ |---|---|---|---|---|
68
+ | `Slides.move` | call | <3 | <3 | <3 |
69
+ | `_TableCellTextFrame.replace_text` | call | <3 | <3 | <3 |
70
+ | `Client.timeout` | get | <3 | <3 | <3 |
71
+ | `DeckSnapshot.get` | call | <3 | <3 | <3 |
72
+ | `Shapes.remove` | call | <3 | <3 | <3 |
73
+ | `ActionSetting.tooltip` | set | <3 | <3 | <3 |
74
+ | `CategoryAxis.major_unit` | set | <3 | <3 | <3 |
75
+ | `DataLabel._dLbl` | get | <3 | <3 | <3 |
76
+ | `DataLabel.number_format_is_linked` | set | <3 | <3 | <3 |
77
+ | `LineFormat._get_or_add_ln` | call | <3 | <3 | <3 |
78
+ | `Picture.insert_picture` | get | <3 | <3 | <3 |
79
+ | `Presentation._buffer` | get | <3 | <3 | <3 |
80
+ | `Presentation.move_slide` | call | <3 | <3 | <3 |
81
+ | `Region.split` | call | <3 | <3 | <3 |
82
+ | `Run._parent` | get | <3 | <3 | <3 |
83
+ | `Shape._placeholder` | get | <3 | <3 | <3 |
84
+ | `Shape._properties` | get | <3 | <3 | <3 |
85
+ | `Shape._shape_id` | get | <3 | <3 | <3 |
86
+ | `Shape.id` | get | <3 | <3 | <3 |
87
+ | `Shape.margin_bottom` | set | <3 | <3 | <3 |
88
+ | `Shape.margin_left` | set | <3 | <3 | <3 |
89
+ | `Shape.margin_right` | set | <3 | <3 | <3 |
90
+ | `Shape.margin_top` | set | <3 | <3 | <3 |
91
+ | `Shape.vertical_anchor` | set | <3 | <3 | <3 |
92
+ | `Shapes.clear_shapes` | call | <3 | <3 | <3 |
93
+ | … 15 more | | | | |
94
+
95
+ Agent code also reached names neither the SDK nor python-pptx has (attributes it hung on SDK
96
+ objects, made-up enum members) on `Client`, `MSO_SHAPE`, `Paragraph`, `Shape`, `SlideLayout`, `Slides`, `_ChartSeries`. The census
97
+ records them only as `?`: a name the code invents may say anything.
98
+
99
+ ## Most-used members (top 60)
100
+
101
+ | # | python-pptx member | SDK class | op | Workspaces | Sessions | Tool calls | Status |
102
+ |---|---|---|---|---|---|---|---|
103
+ | 1 | `Slide.shapes` | `Slide` | get | 10 | 256 | 4644 | works |
104
+ | 2 | `Presentation.slides` | `Presentation` | get | 10 | 255 | 5245 | works |
105
+ | 3 | `Presentation()` | `Presentation` | new | 10 | 244 | 4960 | works |
106
+ | 4 | `Presentation.slide_layouts` | `Presentation` | get | 10 | 185 | 881 | works |
107
+ | 5 | `Shape.text_frame` | `Shape` | get | 9 | 247 | 3053 | works |
108
+ | 6 | `TextFrame.paragraphs` | `TextFrame` | get | 9 | 227 | 2220 | works |
109
+ | 7 | `Pt()` | `Pt` | new | 9 | 224 | 2237 | works |
110
+ | 8 | `Font.color` | `Font` | get | 9 | 216 | 1733 | works |
111
+ | 9 | `_Run.font` | `Run` | get | 9 | 215 | 1954 | works |
112
+ | 10 | `Font.size` | `Font` | set | 9 | 213 | 1897 | works |
113
+ | 11 | `ColorFormat.rgb` | `_FontColorFormat` | set | 9 | 213 | 1636 | works |
114
+ | 12 | `Inches()` | `Inches` | new | 9 | 212 | 2179 | works |
115
+ | 13 | `Font.bold` | `Font` | set | 9 | 211 | 1594 | works |
116
+ | 14 | `RGBColor()` | `RGBColor` | new | 9 | 210 | 2071 | works |
117
+ | 15 | `TextFrame.word_wrap` | `TextFrame` | set | 9 | 205 | 1427 | works |
118
+ | 16 | `_BaseGroupShapes.add_textbox` | `Shapes` | call | 9 | 201 | 1263 | works |
119
+ | 17 | `_Paragraph.alignment` | `Paragraph` | set | 9 | 179 | 1057 | works |
120
+ | 18 | `TextFrame.add_paragraph` | `TextFrame` | call | 9 | 179 | 940 | works |
121
+ | 19 | `_BaseSlide.name` | `SlideLayout` | get | 9 | 172 | 464 | works |
122
+ | 20 | `_Run.text` | `Run` | set | 9 | 169 | 1047 | works |
123
+ | 21 | `BaseShape.shape_type` | `Shape` | get | 9 | 168 | 433 | works |
124
+ | 22 | `Presentation.batch` | `Presentation` | call | 9 | 159 | 2190 | works (Athena extension) |
125
+ | 23 | `TextFrame.margin_left` | `TextFrame` | set | 9 | 153 | 800 | works |
126
+ | 24 | `_Paragraph.add_run` | `Paragraph` | call | 9 | 151 | 805 | works |
127
+ | 25 | `TextFrame.margin_right` | `TextFrame` | set | 9 | 149 | 709 | works |
128
+ | 26 | `Presentation.slide_width` | `Presentation` | get | 9 | 148 | 401 | works |
129
+ | 27 | `TextFrame.margin_top` | `TextFrame` | set | 9 | 147 | 691 | works |
130
+ | 28 | `Presentation.slide_height` | `Presentation` | get | 9 | 142 | 316 | works |
131
+ | 29 | `Font.name` | `Font` | set | 9 | 132 | 816 | works |
132
+ | 30 | `TextFrame.margin_bottom` | `TextFrame` | set | 9 | 131 | 607 | works |
133
+ | 31 | `FillFormat.fore_color` | `FillFormat` | get | 8 | 201 | 1373 | works |
134
+ | 32 | `Shape.fill` | `Shape` | get | 8 | 200 | 1263 | works |
135
+ | 33 | `FillFormat.solid` | `FillFormat` | call | 8 | 199 | 1302 | works |
136
+ | 34 | `ColorFormat.rgb` | `_FillColorFormat` | set | 8 | 199 | 1301 | works |
137
+ | 35 | `Shape.line` | `Shape` | get | 8 | 194 | 1228 | works |
138
+ | 36 | `_BaseGroupShapes.add_shape` | `Shapes` | call | 8 | 189 | 1160 | works |
139
+ | 37 | `BaseShape.name` | `Shape` | get | 8 | 186 | 1454 | works |
140
+ | 38 | `FillFormat.background` | `LineFillFormat` | call | 8 | 181 | 1031 | works |
141
+ | 39 | `LineFormat.fill` | `LineFormat` | get | 8 | 181 | 1033 | works |
142
+ | 40 | `PP_PARAGRAPH_ALIGNMENT.CENTER` | `PP_PARAGRAPH_ALIGNMENT` | enum | 8 | 162 | 856 | works |
143
+ | 41 | `LineFormat.color` | `LineFormat` | get | 8 | 157 | 822 | works |
144
+ | 42 | `ColorFormat.rgb` | `_LineColorFormat` | set | 8 | 155 | 809 | works |
145
+ | 43 | `LineFormat.width` | `LineFormat` | set | 8 | 150 | 752 | works |
146
+ | 44 | `Font.italic` | `Font` | set | 8 | 138 | 808 | works |
147
+ | 45 | `Slides.add_slide` | `Slides` | call | 8 | 134 | 718 | works |
148
+ | 46 | `_Paragraph.space_after` | `Paragraph` | set | 8 | 115 | 448 | works |
149
+ | 47 | `PP_PARAGRAPH_ALIGNMENT.RIGHT` | `PP_PARAGRAPH_ALIGNMENT` | enum | 8 | 114 | 493 | works |
150
+ | 48 | `Slide.validate` | `Slide` | call | 8 | 114 | 999 | works (Athena extension) |
151
+ | 49 | `Presentation.flush` | `Presentation` | call | 8 | 106 | 1176 | works (Athena extension) |
152
+ | 50 | `Presentation.slide_size_inches` | `Presentation` | get | 8 | 75 | 151 | works (Athena extension) |
153
+ | 51 | `_Paragraph.runs` | `Paragraph` | get | 7 | 176 | 1601 | works |
154
+ | 52 | `MSO_VERTICAL_ANCHOR.MIDDLE` | `MSO_ANCHOR` | enum | 7 | 170 | 971 | works |
155
+ | 53 | `TextFrame.vertical_anchor` | `TextFrame` | set | 7 | 164 | 868 | works |
156
+ | 54 | `MSO_AUTO_SHAPE_TYPE.RECTANGLE` | `MSO_SHAPE` | enum | 7 | 136 | 704 | works |
157
+ | 55 | `PP_PARAGRAPH_ALIGNMENT.LEFT` | `PP_PARAGRAPH_ALIGNMENT` | enum | 7 | 122 | 490 | works |
158
+ | 56 | `BaseShape.top` | `Shape` | set | 7 | 118 | 408 | works |
159
+ | 57 | `Table.cell` | `Table` | call | 7 | 112 | 738 | works |
160
+ | 58 | `_Cell.text_frame` | `TableCell` | get | 7 | 109 | 650 | works |
161
+ | 59 | `Table.columns` | `Table` | get | 7 | 108 | 509 | works |
162
+ | 60 | `TextFrame.paragraphs` | `_TableCellTextFrame` | get | 7 | 108 | 636 | works |
163
+
164
+ ## Errors in customers' tool results
165
+
166
+ API-name-only signals from the tool results: the exception class, a missing attribute of an SDK
167
+ class, the feature tag of the SDK's unsupported errors. **Now** checks the SDK as it is today.
168
+
169
+ | Signal | Workspaces | Tool calls | Now |
170
+ |---|---|---|---|
171
+ | `UnsupportedFeatureError` | 8 | 70 | |
172
+ | `UnsupportedFeatureError:[Shape._element]` | 6 | 28 | fixed (the member exists now) |
173
+ | `UnsupportedFeatureError:[cell font.name]` | 6 | 34 | no longer raised |
174
+ | `ValueError` | 6 | 66 | |
175
+ | `AttributeError` | 5 | 83 | |
176
+ | `RemoteError` | 5 | 47 | |
177
+ | `TypeError` | 5 | 35 | |
178
+ | `BatchPartialError` | 4 | 13 | |
179
+ | `ConnectionError` | 4 | 39 | |
180
+ | `FlushAllError` | 4 | 19 | |
181
+ | `MaxRetryError` | 4 | 39 | |
182
+ | `NameError` | 4 | 21 | |
183
+ | `ReadTimeoutError` | 4 | 39 | |
184
+ | `TimeoutError` | 4 | 39 | |
185
+ | `AttributeError:Slides._sldIdLst` | 3 | 10 | fixed (the member exists now) |
186
+ | `FileNotFoundError` | 3 | 4 | |
187
+ | `ImportError` | 3 | 16 | |
188
+ | `IndexError` | 3 | 9 | |
189
+ | `KeyError` | 3 | 5 | |
190
+ | `ModuleNotFoundError` | 3 | 6 | |
191
+ | `UnsupportedFeatureError:[cell font.italic]` | 3 | 7 | no longer raised |
192
+ | `AttributeError:AxisTitleTextFrame.paragraphs` | <3 | <3 | fixed (the member exists now) |
193
+ | `AttributeError:BackgroundFill.type` | <3 | <3 | fixed (the member exists now) |
194
+ | `AttributeError:Run._r` | <3 | <3 | fixed (the member exists now) |
195
+ | `AttributeError:Shapes._spTree` | <3 | <3 | fixed (the member exists now) |
196
+
197
+ ## Class-level parity with python-pptx 1.0.2
198
+
199
+ - 150 python-pptx classes are compared member by member (`tests/test_python_pptx_api_parity.py`); their public members missing from the SDK: 432 allowlisted (XML elements, OPC parts, chart-workbook builders, listed in `docs/API_PARITY_EXCEPTIONS.md`), 0 unexpected.
200
+ - Writes the SDK refuses or ignores, member by member: the generated `unsupported-writes` table in
201
+ `docs/API_PARITY_EXCEPTIONS.md`.
202
+
203
+ ## Method and privacy
204
+
205
+ - `scripts/pull_agent_traces.py` reads the code-tool runs of the production LangSmith project
206
+ (read-only) into a local directory; `scripts/sdk_usage_census.py extract` turns each run's code
207
+ into events and `aggregate` counts them. Raw traces and event files never leave the operator's
208
+ machine.
209
+ - An event is a class, member and parameter name, an enum member name, or `True` / `False` / `None`
210
+ assigned to a property: no literal, no string, no name the agent's code defines. Workspace and
211
+ session ids are hashed with a per-run salt and dropped after counting.
212
+ - Population: a call is Athena staff when its signed-in user is staff, or when it ran without a
213
+ signed-in user (an automation) in a workspace where staff made most signed-in calls.
214
+ - Typing: each expression is typed from the SDK's annotations (and, where a member has none, from
215
+ the classes its `return` statements build). A member only a subclass defines is credited to that
216
+ subclass (`downcast`); a name the code never assigns is typed by agents' naming convention
217
+ (`prs`, `slide`, `tf`, `cell`: the executor keeps globals between calls) (`heuristic`).
218
+ - Limits: code that reaches the SDK through objects the analysis cannot type (dict lookups, helper
219
+ classes) is not counted, so counts are lower bounds; customer VPC environments keep their own
220
+ traces and are not in this census.
@@ -2,6 +2,223 @@
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
+
30
+ ## 0.19.0 — `Shapes.add_linked_chart()`: a chart whose data is a source range
31
+
32
+ - `slide.shapes.add_linked_chart(chart_type, source, anchor, left=, top=,
33
+ width=, height=, series_in='columns', freshness_mode='athena_refreshable',
34
+ version_policy=None, title=None, name=None)` sends `AddLinkedChart`. The
35
+ studio reads the range and writes the chart's categories and series from
36
+ it, and the chart refreshes from the range as a linked table does.
37
+ - An Athena studio-linking extension with no python-pptx counterpart, beside
38
+ `add_linked_table()` and `add_linked_ole_object()`, added with explicit
39
+ approval (2026-10-20). See `docs/API_PARITY_EXCEPTIONS.md`.
40
+ - Column, bar, line, area, pie and doughnut chart types; a scatter, bubble,
41
+ radar or Office 2016 type raises `UnsupportedFeatureError`.
42
+ - Needs a studio with `AddLinkedChart` in its command schema (pptx-studio
43
+ since the linked-chart refresh work, #45113).
44
+
45
+ ## 0.19.0 — a text write keeps what someone typed after the SDK read the box
46
+
47
+ - A write of a box's text (`text_frame.text`, `paragraph.text`,
48
+ `text_frame.clear()`, `add_paragraph()`, a hyperlink change, …) sends
49
+ `SetText` with `baseText`: the frame's text as the SDK read it from the
50
+ snapshot, or last wrote it (python-pptx's `text_frame.text`, a line break
51
+ as `\v`). A run's (`run.text`, `run.upper()`, …) sends `SetRunText` with the
52
+ run's. When the box holds other text, someone changed it between the read
53
+ and the write (an executor run sends its writes when the script ends), and
54
+ the studio merges three ways: the SDK's change goes onto the box's current
55
+ text, so their typing is kept, and a word they deleted stays deleted. Where
56
+ both changed the same words, both versions are kept, theirs first (#44914,
57
+ PR 8).
58
+ - No public API changes, and python-pptx code reads and writes as before:
59
+ `text_frame.text` reads what the SDK wrote until the deck is read again.
60
+ - After `text_frame.replace_text()` (the studio replaces in the text it finds)
61
+ the frame's next write sends no base, until the frame is read again or
62
+ written whole.
63
+ - Needs a studio that merges (pptx-studio with `baseText` in its command
64
+ schema). An older studio drops the field, as it drops any key it does not
65
+ declare, and writes the text over the box as before.
66
+
67
+ ## 0.19.0 — chart and axis titles in lines read and write as python-pptx's
68
+
69
+ - `chart.chart_title.text_frame` and `axis.axis_title.text_frame` hold
70
+ python-pptx's paragraphs and line breaks. `text_frame.text = "a\nb\vc"` is a
71
+ paragraph per `\n`-separated line and a line break for each `\v`, and the
72
+ saved deck gets them so (it got one run whose text held the line feeds);
73
+ `paragraph.text = "a\nb"` makes both line breaks. `text_frame.text` reads
74
+ `\n` between paragraphs and `\v` for a line break, `paragraphs` one per
75
+ line (an axis title had one), and a paragraph's `runs` leave its line breaks
76
+ out. An uploaded deck's titles read the same way, where a line break
77
+ (`a:br`) read as `\n`.
78
+ - A chart title's runs styled across its lines (`paragraphs[i].runs[j].font`,
79
+ a paragraph from `add_paragraph()`) reach the deck in their fonts, each in
80
+ its paragraph: `SetChartTitleRich` carries each paragraph break as a run of
81
+ `"\n"` and each line break as a run of `"\v"`. They were sent as one
82
+ paragraph, the paragraphs run together.
83
+ - Departures (a run's own text with a line feed, the font kept on replaced
84
+ text, trimmed chart titles, fields read as runs):
85
+ `docs/API_PARITY_EXCEPTIONS.md`, "Chart and axis titles in lines".
86
+ - Needs a studio that writes titles in lines; an older one writes each
87
+ paragraph break as a line feed inside a run, and a line break as a vertical
88
+ tab the deck's XML cannot hold.
89
+
90
+ ## 0.19.0 — a chart point's own line reads and writes as python-pptx's
91
+
92
+ - `series.points[i].format.line` is the point's own line
93
+ (`c:dPt/c:spPr/a:ln`): the outline of its bar, slice or bubble, or on a
94
+ line or XY series the segment ending at it. `color.rgb`, `width`,
95
+ `dash_style`, `fill.background()` and `fill.solid()` emit a `SetPointLine`
96
+ chart patch for that point (the series' line is left alone), which the deck
97
+ gets as python-pptx writes it; they warned that they were ignored and sent
98
+ nothing. Reads return the point's own line as the uploaded file has it (an
99
+ uploaded slice's white separator reads `MSO_FILL.SOLID`, `FFFFFF`, its
100
+ width; a point repeating its series' line reads that copy), and `width`
101
+ reads `0` for a point without one, as python-pptx's does. `color.theme_color` still warns. Departures:
102
+ `docs/API_PARITY_EXCEPTIONS.md`.
103
+ - Needs a studio with the `SetPointLine` op; an older one rejects the batch
104
+ that carries it.
105
+
106
+ ## 0.19.0 — converting an uploaded file keeps its id
107
+
108
+ - Opening an uploaded `.pptx`/`.potx` that was never converted says to convert
109
+ it with `convert_powerpoint_to_athena_presentation` and then open the same id:
110
+ the upload itself becomes the presentation. It said the conversion creates a
111
+ NEW presentation with its own id, which Athena's conversion no longer does.
112
+
113
+ ## 0.19.0 — a chart's date axis reads as python-pptx's `DateAxis`
114
+
115
+ - `chart.category_axis` on an uploaded chart whose category axis is a
116
+ `c:dateAx` (and that has no `c:catAx`) is a `DateAxis`, as in python-pptx
117
+ 1.0.2, its `category_type` `XL_CATEGORY_TYPE.TIME_SCALE`. It was a
118
+ `CategoryAxis` that read nothing of the axis (the studio did not read date
119
+ axes): its number format, its link to the source, its title, gridlines,
120
+ reversal and visibility now read back as the deck has them. Its edits reach
121
+ the date axis, as before.
122
+ - `CategoryAxis.category_type` reads `XL_CATEGORY_TYPE.CATEGORY_SCALE`, as
123
+ python-pptx's does; it read `None`. After the SDK's own setter it reads the
124
+ type written.
125
+ - Needs a studio that reads date axes (`AxisSpec.dateAxis`); with an older one
126
+ `category_axis` stays a `CategoryAxis`.
127
+
128
+ ## 0.19.0 — table cells hold paragraphs and runs
129
+
130
+ - A table cell's text frame holds python-pptx's paragraphs and runs.
131
+ `cell.text_frame.add_paragraph()` and `paragraph.add_run()` add a paragraph or
132
+ a run, where they handed back the cell's single paragraph or run with a
133
+ warning: a "**Label:** value" cell built from two runs kept only the last
134
+ run's text, in its formatting, and a cell built paragraph by paragraph kept
135
+ only its last line. These were the most-used gaps in customers' agent code
136
+ (`API_PARITY_REPORT.md`). Each run has its own `font` (`bold`, `italic`,
137
+ `size`, `name`, `color.rgb`, and now `underline`, which raised), each
138
+ paragraph its own `alignment`, `level`, `line_spacing`, `space_before` and
139
+ `space_after` (which warned and were dropped) and `font` (its run defaults,
140
+ `a:defRPr`), and `text_frame.clear()` / `paragraph.clear()` keep the
141
+ paragraph's properties, as in python-pptx. Assigning `None` removes a run's
142
+ or paragraph's own value, and a run added with no text stays an empty run. A
143
+ cell holding more than one run is sent whole (`SetTableCell.paragraphs`, with
144
+ its text beside it for a studio that predates the field). On an uploaded
145
+ table a kept run keeps its own run properties, a kept paragraph its own
146
+ paragraph properties and end mark, and the export writes only what the edit
147
+ changed; an uploaded cell's runs read their own family, italic and underline.
148
+ Needs a studio with `SetTableCell.paragraphs`; an older one applies the
149
+ cell's text alone.
150
+ - `cell.text_frame.word_wrap = True` no longer warns: a table cell's text wraps
151
+ at the cell width in PowerPoint and the studio, which is what `True` asks for.
152
+ - `run.text = …` on a table cell holding one run keeps the run, as python-pptx's
153
+ does: only its text changes, and the font, size, colour, bold, italic and link
154
+ it had in the uploaded deck (or that this session wrote) stay, with its
155
+ paragraph's alignment. It retyped the cell (`cell.text`), which dropped every
156
+ property read from the deck: a template's styled cell came out plain. The
157
+ stored run is kept (`SetTableCell.paragraphs`, `keep`) and only its text is
158
+ sent; a studio that predates the field applies the text beside it, which
159
+ keeps the run's style. An uploaded cell's empty run (a template's run
160
+ formatted for text to come) is a run too: given text (or an underline) it
161
+ keeps its properties and link, and `paragraph.add_run()` adds a run after it,
162
+ as python-pptx's does, where the text went in a new, plain run (or in that
163
+ run). On a table the SDK built, the font written on an empty cell's run in an
164
+ earlier session goes with the text it is given, as this session's does.
165
+ - On a table the SDK built, a run's bold or colour written before a family, an
166
+ alignment or italic reads back after a refresh: it read `None`, while the
167
+ studio drew it and the downloaded deck had it.
168
+ - With a studio from this release, `font.bold = False` (`italic`, `underline`)
169
+ on a table the SDK built reaches the downloaded deck as `b="0"` (`i="0"`,
170
+ `u="none"`), as python-pptx writes it: PowerPoint drew a header run turned off
171
+ in the table style's bold, where the studio drew it regular. The fix is in the
172
+ studio's export, so it applies to every SDK version once the studio deploys;
173
+ an uploaded table already wrote them.
174
+ `tests/test_table_cell_formatting_parity.py` compares both with stock
175
+ python-pptx in session, after a refresh and in the exported XML.
176
+ - `shape.shadow.inherit = False` on a shape without a shadow of its own gives
177
+ it python-pptx's empty effect list (`<a:effectLst/>` in the downloaded
178
+ deck), so it casts no shadow, the one its theme effect style casts included,
179
+ and `inherit` reads `False` after the deck is reopened. It changed nothing
180
+ before: PowerPoint kept showing the style's shadow (an Office 2007 theme's
181
+ second effect style, which `add_shape()` names), and a reopened deck read
182
+ `True`. `inherit = True` removes the shape's own shadow and that list, and
183
+ `shadow.visible = False` on a shape with a shadow of its own keeps its
184
+ effect list, emptied, as deleting the `<a:outerShdw>` would (the export
185
+ dropped the list, and PowerPoint drew the style's shadow in its place). An
186
+ uploaded shape with an effect list of its own, an empty `<a:effectLst/>`
187
+ included, reads `inherit == False` as in python-pptx (it read `True`), and
188
+ `inherit = True` removes that list, every effect in it, from the downloaded
189
+ deck. A shadow the shape takes from its style is not reported as its own:
190
+ `inherit` stays `True` and `visible` `False`. Needs a studio with
191
+ `SetShapeShadow.inherit`; an older one ignores the field.
192
+ - `line.dash_style` on an uploaded line reads its `<a:prstDash>` as python-pptx
193
+ 1.0.2 does: `sysDash` is `SQUARE_DOT`, `sysDot` `ROUND_DOT`, `lgDashDotDot`
194
+ `DASH_DOT_DOT` and an explicit `solid` `SOLID`, where the studio's import read
195
+ `sysDash` as `DASH`, `sysDashDot` as `DASH_DOT` and `lgDashDotDot` as no dash
196
+ at all (on a connector, `LONG_DASH_DOT`), and a shape's explicit `solid` as
197
+ `None`. The presets python-pptx has no member for (`dot`, `sysDashDot`,
198
+ `sysDashDotDot`) raise its `ValueError`, as python-pptx does. A dash set in
199
+ the session reads back as the preset the studio writes for it. Needs a
200
+ studio that sends `strokeDashPreset`; with an older one, and for decks
201
+ imported before it, `dash_style` reads as before.
202
+ - `font.fill.type` reads a run's own fill as python-pptx does:
203
+ `MSO_FILL.GRADIENT` for gradient text (`<a:gradFill>`), `MSO_FILL.BACKGROUND`
204
+ for hollow text (`<a:noFill/>`), `MSO_FILL.SOLID` for a colour of its own and
205
+ `None` when the run inherits its fill. It read `None` unless `fill.solid()`
206
+ had been called in the session, and then the string `"solid"`. Setting
207
+ `font.color` on gradient or hollow text makes it solid, as in python-pptx.
208
+ The glyph outline (`<a:ln>`, which python-pptx has no API for) and a
209
+ gradient are carried unchanged when the SDK resends a run. Needs a studio
210
+ that stores `textFill`; with an older one, and for decks imported before it
211
+ until they are imported again, `fill.type` reads as before.
212
+ - `font.fill.background()` makes the text of a run or a paragraph's font
213
+ transparent (`<a:noFill/>`), as python-pptx does: hollow glyphs, drawn by
214
+ their outline alone (an uploaded run's `<a:ln>`) and invisible without one.
215
+ It raised. A colour set afterwards fills the text again, within one batch
216
+ too. Table and chart text still raise. `font.fill.solid()` on hollow or
217
+ gradient text removes that fill, and the text takes the colour it inherits
218
+ until a colour is set (python-pptx writes a colourless `<a:solidFill/>`); it
219
+ only recorded the fill type. Needs a studio that stores `textFill`; an older
220
+ one ignores the writes.
221
+
5
222
  ## 0.18.0 — chart.chart_area / chart.plot_area format and add_chart(name=)
6
223
 
7
224
  - `chart.chart_area.format` and `chart.plot_area.format` now round-trip fill
@@ -0,0 +1,112 @@
1
+ # athena-python-pptx ↔ python-pptx — open parity questions
2
+
3
+ The open gaps and design questions between this SDK and python-pptx 1.0.2, in the order to work
4
+ them: by how much customers' agent code uses each. Sister documents:
5
+
6
+ - [`API_PARITY_REPORT.md`](API_PARITY_REPORT.md) — generated: the usage census joined with the
7
+ surface audit, with every used member's status.
8
+ - [`docs/API_PARITY_EXCEPTIONS.md`](docs/API_PARITY_EXCEPTIONS.md) — the deviations this SDK makes on
9
+ purpose, and the generated tables of the members it refuses or ignores.
10
+
11
+ Usage comes from `docs/sdk_usage_census.json` (`scripts/sdk_usage_census.py`): the customer
12
+ workspaces and sessions whose agent code reached a member in the census window. A gap no customer
13
+ reached stays listed, unscheduled, until a later census moves it.
14
+
15
+ ## Used and broken today
16
+
17
+ <!-- BEGIN GENERATED: ranked-gaps (scripts/generate_parity_report.py; do not edit by hand) -->
18
+ Census: the 60 days from 2026-08-16 to 2026-10-14; counts are customer workspaces | sessions (`<3`: fewer than three workspaces). Full table: `API_PARITY_REPORT.md`.
19
+
20
+ | # | Member | SDK class | Workspaces | Sessions | What the SDK does |
21
+ |---|---|---|---|---|---|
22
+ | 1 | `Shapes._spTree` | `Shapes` | <3 | <3 | raises: in 4 of 8 probed contexts; athena-python-pptx mirrors a slide's own shape tree, slide.shapes._spTree; a notes slide's, layout's or master's is not mirrored. |
23
+ | 2 | `Chart._chartSpace` | `Chart` | <3 | <3 | raises: python-pptx XML internal: Use the Chart API: chart.plots, chart.series, chart.replace_data(chart_data), chart.has_title / chart.chart_title, chart.value_axis, chart.category_axis. |
24
+ | 3 | `Slide._element` | `Slide` | <3 | <3 | raises: python-pptx XML internal: Use the object's own properties and methods. |
25
+ | 4 | `Subshape.part` | `Paragraph` | <3 | <3 | raises: athena-python-pptx edits a deck that lives on the PPTX Studio server, so there are no OPC package parts or relationships to reach. |
26
+ | 5 | `_ChartSeries._element` | `_ChartSeries` | <3 | <3 | raises: python-pptx XML internal: Use the object's own properties and methods. |
27
+ | 6 | `ColorFormat.brightness` | `_FillColorFormat` | <3 | <3 | raises: Shape fills carry no brightness in the Athena studio (SetShapeStyle has no lumMod / lumOff). |
28
+ <!-- END GENERATED: ranked-gaps -->
29
+
30
+ ## Open, in usage order
31
+
32
+ ### python-pptx XML internals agents reach
33
+
34
+ In the table above, each from fewer than three workspaces: `chart._chartSpace`, `slide._element`,
35
+ `series._element`, `paragraph.part`, and `shapes._spTree` where it is not a slide's own (a notes
36
+ slide's, layout's or master's shapes; the census cannot tell which the agents read). Each raises
37
+ `UnsupportedAttributeError` naming the supported API. The text, table and shape-tree mirrors (0.17)
38
+ cover the recipes customers used most.
39
+
40
+ **Question:** mirror a chart's or a slide's XML only when a census shows a recipe at three or
41
+ more workspaces.
42
+
43
+ ### Shape fill brightness
44
+
45
+ In the table above, from fewer than three workspaces: `shape.fill.fore_color.brightness = …` raises; the
46
+ studio's fill model has no `lumMod` / `lumOff`.
47
+
48
+ ### Members agents invent
49
+
50
+ Not parity gaps (python-pptx raises too), listed in the report's "Members agents call that
51
+ python-pptx does not have": a misspelt `MSO_SHAPE.ISOCELES_TRIANGLE`, `prs.slides.move(…)` /
52
+ `prs.move_slide(…)`, `shapes.remove(…)`, `category_axis.major_unit`, and agents setting the SDK's
53
+ private HTTP client timeouts (`Client.timeout`, and attributes of their own on `Client`), which
54
+ matches the read timeouts in the error signals.
55
+
56
+ **Question:** which of these deserve a teaching `AttributeError` (as `GUESSED_MEMBERS` gives the
57
+ slide-move guesses) or a line in the skill text.
58
+
59
+ ## Open, with no customer use in the window (unscheduled)
60
+
61
+ - **3-D, stock and surface charts** — `add_chart()` raises `UnsupportedFeatureError`. The deck
62
+ census found no stock chart in any customer deck either.
63
+ - **Gradient and pattern text** (`Font.fill.gradient()` / `.patterned()`) — raise.
64
+ - **Notes-slide shapes and placeholders** (`NotesSlide.shapes`, `.placeholders`,
65
+ `.notes_placeholder`) — empty collections; `notes_slide.notes_text_frame` carries notes text.
66
+ `prs.notes_master` raises.
67
+ - **Layout and master backgrounds** — read-only; `fill.solid()` and colour writes raise.
68
+ - **Table-cell theme colours and hyperlinks** — `font.color.theme_color`,
69
+ `fill.fore_color.theme_color` and `run.hyperlink.address` on a cell raise; a cell's link is
70
+ cell-wide (`SetTableCell.hyperlinkUrl`), so every run of a cell reads it.
71
+ - **Table-cell `word_wrap = False` / `None` and `auto_size`** — warn once and keep the value
72
+ locally. `word_wrap = True` is accepted (see Resolved).
73
+ - **`MSO_LANGUAGE_ID.MIXED`** — exists for parity; nothing returns it (runs are addressed one at a
74
+ time).
75
+ - **`Area3DPlot`** behaves as `AreaPlot`.
76
+ - **`XyChartData.add_series(name, values)`** — signature drift from python-pptx's
77
+ `add_series(name, number_format=None)` (documented in `docs/API_PARITY_EXCEPTIONS.md`).
78
+
79
+ ## Resolved since the 2026-05 report
80
+
81
+ - **Table cells hold python-pptx's paragraphs and runs** — the census's most-used gap
82
+ (`_TableCellParagraph.add_run` 3 workspaces / 23 sessions, `_TableCellTextFrame.add_paragraph`
83
+ 3 / 10). `add_run()` and `add_paragraph()` add a run or paragraph, each run keeps its own font
84
+ (`underline` included), a paragraph carries its own alignment, level, line spacing and space
85
+ before / after, and `clear()` keeps the first paragraph's properties, as in python-pptx. The SDK
86
+ sends the cell's whole text body (`SetTableCell.paragraphs`); an uploaded cell's runs keep their
87
+ own XML and take only what an edit wrote. `tests/test_table_cell_runs_parity.py` compares the
88
+ reads with stock python-pptx in session, after a refresh and in the exported deck.
89
+ - **`cell.text_frame.word_wrap = True`** (3 workspaces / 19 sessions) is accepted without a
90
+ warning: PowerPoint and the studio wrap a cell's text at the cell width, which is what `True`
91
+ asks for. It reads back in the session; after a refresh it reads `None` (python-pptx reads the
92
+ `wrap="square"` it wrote).
93
+ - **Chart patch ops** — axis scale, gridlines, tick-label and legend fonts, legend position, chart
94
+ font and style, data labels, markers, series and point fills, trendlines and error bars all
95
+ round-trip through chart patches. What the studio still ignores is the warn-once list in
96
+ `docs/API_PARITY_EXCEPTIONS.md` (`unsupported-writes`).
97
+ - **`Legend.include_in_layout`** — `False`, the value customers' agents assign, is what the studio
98
+ draws; only `True` (legend over the plot) warns.
99
+ - **`Picture.image`** returns python-pptx's `Image` with the picture's bytes (0.18.0).
100
+ - **`GroupShape.shapes`** lists the group's member shapes.
101
+ - **Detached chart fonts** (`chart.font`, `legend.font`, `tick_labels.font`) send their changes.
102
+ - **XML recipes** — `prs.slides._sldIdLst`, `shape._element` / `_spTree`, `text_frame._txBody`,
103
+ `paragraph._p`, `run._r`, `table._tbl`, `cell._tc` are real mirrors (0.17.0); the census's error
104
+ signals show customers hitting the old refusals (`Shapes._spTree`, `Slides._sldIdLst`,
105
+ `Run._r`) before that release.
106
+ - **Table-cell font name and italic** — supported; the `UnsupportedFeatureError` customers hit for
107
+ `[cell font.name]` / `[cell font.italic]` earlier in the window no longer exists.
108
+ - **`Connector.begin_connect(None)`** raises instead of recording an empty endpoint.
109
+ - **Private-class naming** — python-pptx's public names (`AxisTitle`, `TickLabels`, `Legend`, …) are
110
+ the SDK's (Tranche B); `_Paragraph`, `_Run`, `_Cell` import from their python-pptx paths.
111
+ - **`Font.language_id`** (with `MSO_LANGUAGE_ID.NONE` removing the language) and the language-tag
112
+ mapping, **`shape.click_action`** returning `ActionSetting`, and **core properties** — done.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: athena-python-pptx
3
- Version: 0.18.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
@@ -869,6 +869,15 @@ print(info['wsUrl'])
869
869
  print(info['authToken'])
870
870
  ```
871
871
 
872
+ People can edit a deck while your code runs, and your writes reach it when
873
+ the batch is sent (in Athena's executor, when the script ends). A write of a
874
+ box's text (`text_frame.text`, `paragraph.text`, `run.text`, …) names the text
875
+ it replaces as the SDK read it, so the studio keeps what someone typed into
876
+ the box after that read: your change goes onto the box's current text. Where
877
+ you both changed the same words, both versions stay, theirs first. Nothing
878
+ changes in the code you write; `text_frame.text` reads what you wrote until
879
+ the deck is read again (`prs.refresh()`).
880
+
872
881
  ---
873
882
 
874
883
  ## Supported Features