athena-python-pptx 0.18.0__tar.gz → 0.19.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.19.0}/AGENTS.md +35 -7
  2. athena_python_pptx-0.19.0/API_PARITY_REPORT.md +220 -0
  3. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/CHANGELOG.md +192 -0
  4. athena_python_pptx-0.19.0/PARITY_QUESTIONS.md +112 -0
  5. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/PKG-INFO +10 -1
  6. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/README.md +9 -0
  7. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/docs/API_PARITY_EXCEPTIONS.md +249 -60
  8. athena_python_pptx-0.19.0/docs/sdk_usage_census.json +15382 -0
  9. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/__init__.py +1 -1
  10. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/_unsupported.py +1 -5
  11. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/batching.py +11 -0
  12. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/commands.py +150 -0
  13. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/enum/lang.py +204 -41
  14. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/oxml/simpletypes.py +3 -3
  15. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/presentation.py +3 -3
  16. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/shapes/__init__.py +1718 -286
  17. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/text/__init__.py +165 -16
  18. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/typing.py +4 -0
  19. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pyproject.toml +1 -1
  20. athena_python_pptx-0.18.0/API_PARITY_REPORT.md +0 -165
  21. athena_python_pptx-0.18.0/PARITY_QUESTIONS.md +0 -304
  22. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/.gitignore +0 -0
  23. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/CLAUDE.md +0 -0
  24. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/DEV-GUIDE.md +0 -0
  25. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/docs/athena-api.json +0 -0
  26. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/docs/athena-api.md +0 -0
  27. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/_athena_extension.py +0 -0
  28. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/_citations.py +0 -0
  29. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/_ptc.py +0 -0
  30. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/_references.py +0 -0
  31. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/action.py +0 -0
  32. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/chart/__init__.py +0 -0
  33. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/chart/axis.py +0 -0
  34. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/chart/category.py +0 -0
  35. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/chart/chart.py +0 -0
  36. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/chart/data.py +0 -0
  37. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/chart/datalabel.py +0 -0
  38. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/chart/legend.py +0 -0
  39. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/chart/marker.py +0 -0
  40. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/chart/plot.py +0 -0
  41. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/chart/point.py +0 -0
  42. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/chart/series.py +0 -0
  43. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/chart/xlsx.py +0 -0
  44. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/client.py +0 -0
  45. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/decorators.py +0 -0
  46. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/dml/__init__.py +0 -0
  47. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/dml/chtfmt.py +0 -0
  48. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/dml/color.py +0 -0
  49. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/dml/effect.py +0 -0
  50. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/dml/fill.py +0 -0
  51. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/dml/line.py +0 -0
  52. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/docgen.py +0 -0
  53. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/enum/__init__.py +0 -0
  54. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/enum/action.py +0 -0
  55. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/enum/chart.py +0 -0
  56. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/enum/dml.py +0 -0
  57. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/enum/shapes.py +0 -0
  58. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/enum/text.py +0 -0
  59. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/errors.py +0 -0
  60. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/exc.py +0 -0
  61. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/generated/__init__.py +0 -0
  62. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/generated/autoshape_types.py +0 -0
  63. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/kit.py +0 -0
  64. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/media.py +0 -0
  65. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/oxml/__init__.py +0 -0
  66. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/oxml/_mirror.py +0 -0
  67. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/oxml/_tbl.py +0 -0
  68. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/oxml/_txbody.py +0 -0
  69. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/oxml/ns.py +0 -0
  70. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/oxml/presentation.py +0 -0
  71. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/oxml/shapes/__init__.py +0 -0
  72. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/oxml/shapes/autoshape.py +0 -0
  73. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/oxml/shapes/connector.py +0 -0
  74. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/oxml/shapes/graphfrm.py +0 -0
  75. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/oxml/shapes/groupshape.py +0 -0
  76. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/oxml/shapes/picture.py +0 -0
  77. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/oxml/shapes/shared.py +0 -0
  78. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/oxml/table.py +0 -0
  79. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/oxml/text.py +0 -0
  80. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/oxml/xmlchemy.py +0 -0
  81. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/package.py +0 -0
  82. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/parts/__init__.py +0 -0
  83. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/parts/_base.py +0 -0
  84. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/parts/chart.py +0 -0
  85. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/parts/coreprops.py +0 -0
  86. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/parts/embeddedpackage.py +0 -0
  87. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/parts/image.py +0 -0
  88. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/parts/media.py +0 -0
  89. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/parts/presentation.py +0 -0
  90. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/parts/slide.py +0 -0
  91. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/shapes/autoshape.py +0 -0
  92. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/shapes/base.py +0 -0
  93. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/shapes/connector.py +0 -0
  94. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/shapes/freeform.py +0 -0
  95. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/shapes/graphfrm.py +0 -0
  96. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/shapes/group.py +0 -0
  97. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/shapes/picture.py +0 -0
  98. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/shapes/placeholder.py +0 -0
  99. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/shapes/shapetree.py +0 -0
  100. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/shared.py +0 -0
  101. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/slide.py +0 -0
  102. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/slides.py +0 -0
  103. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/spec.py +0 -0
  104. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/table.py +0 -0
  105. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/text/fonts.py +0 -0
  106. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/text/layout.py +0 -0
  107. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/text/text.py +0 -0
  108. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/types.py +0 -0
  109. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.0}/pptx/units.py +0 -0
  110. {athena_python_pptx-0.18.0 → athena_python_pptx-0.19.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
+ | … 17 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,198 @@
2
2
 
3
3
  All notable changes to `athena-python-pptx` are documented in this file.
4
4
 
5
+ ## 0.19.0 — `Shapes.add_linked_chart()`: a chart whose data is a source range
6
+
7
+ - `slide.shapes.add_linked_chart(chart_type, source, anchor, left=, top=,
8
+ width=, height=, series_in='columns', freshness_mode='athena_refreshable',
9
+ version_policy=None, title=None, name=None)` sends `AddLinkedChart`. The
10
+ studio reads the range and writes the chart's categories and series from
11
+ it, and the chart refreshes from the range as a linked table does.
12
+ - An Athena studio-linking extension with no python-pptx counterpart, beside
13
+ `add_linked_table()` and `add_linked_ole_object()`, added with explicit
14
+ approval (2026-10-20). See `docs/API_PARITY_EXCEPTIONS.md`.
15
+ - Column, bar, line, area, pie and doughnut chart types; a scatter, bubble,
16
+ radar or Office 2016 type raises `UnsupportedFeatureError`.
17
+ - Needs a studio with `AddLinkedChart` in its command schema (pptx-studio
18
+ since the linked-chart refresh work, #45113).
19
+
20
+ ## 0.19.0 — a text write keeps what someone typed after the SDK read the box
21
+
22
+ - A write of a box's text (`text_frame.text`, `paragraph.text`,
23
+ `text_frame.clear()`, `add_paragraph()`, a hyperlink change, …) sends
24
+ `SetText` with `baseText`: the frame's text as the SDK read it from the
25
+ snapshot, or last wrote it (python-pptx's `text_frame.text`, a line break
26
+ as `\v`). A run's (`run.text`, `run.upper()`, …) sends `SetRunText` with the
27
+ run's. When the box holds other text, someone changed it between the read
28
+ and the write (an executor run sends its writes when the script ends), and
29
+ the studio merges three ways: the SDK's change goes onto the box's current
30
+ text, so their typing is kept, and a word they deleted stays deleted. Where
31
+ both changed the same words, both versions are kept, theirs first (#44914,
32
+ PR 8).
33
+ - No public API changes, and python-pptx code reads and writes as before:
34
+ `text_frame.text` reads what the SDK wrote until the deck is read again.
35
+ - After `text_frame.replace_text()` (the studio replaces in the text it finds)
36
+ the frame's next write sends no base, until the frame is read again or
37
+ written whole.
38
+ - Needs a studio that merges (pptx-studio with `baseText` in its command
39
+ schema). An older studio drops the field, as it drops any key it does not
40
+ declare, and writes the text over the box as before.
41
+
42
+ ## 0.19.0 — chart and axis titles in lines read and write as python-pptx's
43
+
44
+ - `chart.chart_title.text_frame` and `axis.axis_title.text_frame` hold
45
+ python-pptx's paragraphs and line breaks. `text_frame.text = "a\nb\vc"` is a
46
+ paragraph per `\n`-separated line and a line break for each `\v`, and the
47
+ saved deck gets them so (it got one run whose text held the line feeds);
48
+ `paragraph.text = "a\nb"` makes both line breaks. `text_frame.text` reads
49
+ `\n` between paragraphs and `\v` for a line break, `paragraphs` one per
50
+ line (an axis title had one), and a paragraph's `runs` leave its line breaks
51
+ out. An uploaded deck's titles read the same way, where a line break
52
+ (`a:br`) read as `\n`.
53
+ - A chart title's runs styled across its lines (`paragraphs[i].runs[j].font`,
54
+ a paragraph from `add_paragraph()`) reach the deck in their fonts, each in
55
+ its paragraph: `SetChartTitleRich` carries each paragraph break as a run of
56
+ `"\n"` and each line break as a run of `"\v"`. They were sent as one
57
+ paragraph, the paragraphs run together.
58
+ - Departures (a run's own text with a line feed, the font kept on replaced
59
+ text, trimmed chart titles, fields read as runs):
60
+ `docs/API_PARITY_EXCEPTIONS.md`, "Chart and axis titles in lines".
61
+ - Needs a studio that writes titles in lines; an older one writes each
62
+ paragraph break as a line feed inside a run, and a line break as a vertical
63
+ tab the deck's XML cannot hold.
64
+
65
+ ## 0.19.0 — a chart point's own line reads and writes as python-pptx's
66
+
67
+ - `series.points[i].format.line` is the point's own line
68
+ (`c:dPt/c:spPr/a:ln`): the outline of its bar, slice or bubble, or on a
69
+ line or XY series the segment ending at it. `color.rgb`, `width`,
70
+ `dash_style`, `fill.background()` and `fill.solid()` emit a `SetPointLine`
71
+ chart patch for that point (the series' line is left alone), which the deck
72
+ gets as python-pptx writes it; they warned that they were ignored and sent
73
+ nothing. Reads return the point's own line as the uploaded file has it (an
74
+ uploaded slice's white separator reads `MSO_FILL.SOLID`, `FFFFFF`, its
75
+ width; a point repeating its series' line reads that copy), and `width`
76
+ reads `0` for a point without one, as python-pptx's does. `color.theme_color` still warns. Departures:
77
+ `docs/API_PARITY_EXCEPTIONS.md`.
78
+ - Needs a studio with the `SetPointLine` op; an older one rejects the batch
79
+ that carries it.
80
+
81
+ ## 0.19.0 — converting an uploaded file keeps its id
82
+
83
+ - Opening an uploaded `.pptx`/`.potx` that was never converted says to convert
84
+ it with `convert_powerpoint_to_athena_presentation` and then open the same id:
85
+ the upload itself becomes the presentation. It said the conversion creates a
86
+ NEW presentation with its own id, which Athena's conversion no longer does.
87
+
88
+ ## 0.19.0 — a chart's date axis reads as python-pptx's `DateAxis`
89
+
90
+ - `chart.category_axis` on an uploaded chart whose category axis is a
91
+ `c:dateAx` (and that has no `c:catAx`) is a `DateAxis`, as in python-pptx
92
+ 1.0.2, its `category_type` `XL_CATEGORY_TYPE.TIME_SCALE`. It was a
93
+ `CategoryAxis` that read nothing of the axis (the studio did not read date
94
+ axes): its number format, its link to the source, its title, gridlines,
95
+ reversal and visibility now read back as the deck has them. Its edits reach
96
+ the date axis, as before.
97
+ - `CategoryAxis.category_type` reads `XL_CATEGORY_TYPE.CATEGORY_SCALE`, as
98
+ python-pptx's does; it read `None`. After the SDK's own setter it reads the
99
+ type written.
100
+ - Needs a studio that reads date axes (`AxisSpec.dateAxis`); with an older one
101
+ `category_axis` stays a `CategoryAxis`.
102
+
103
+ ## 0.19.0 — table cells hold paragraphs and runs
104
+
105
+ - A table cell's text frame holds python-pptx's paragraphs and runs.
106
+ `cell.text_frame.add_paragraph()` and `paragraph.add_run()` add a paragraph or
107
+ a run, where they handed back the cell's single paragraph or run with a
108
+ warning: a "**Label:** value" cell built from two runs kept only the last
109
+ run's text, in its formatting, and a cell built paragraph by paragraph kept
110
+ only its last line. These were the most-used gaps in customers' agent code
111
+ (`API_PARITY_REPORT.md`). Each run has its own `font` (`bold`, `italic`,
112
+ `size`, `name`, `color.rgb`, and now `underline`, which raised), each
113
+ paragraph its own `alignment`, `level`, `line_spacing`, `space_before` and
114
+ `space_after` (which warned and were dropped) and `font` (its run defaults,
115
+ `a:defRPr`), and `text_frame.clear()` / `paragraph.clear()` keep the
116
+ paragraph's properties, as in python-pptx. Assigning `None` removes a run's
117
+ or paragraph's own value, and a run added with no text stays an empty run. A
118
+ cell holding more than one run is sent whole (`SetTableCell.paragraphs`, with
119
+ its text beside it for a studio that predates the field). On an uploaded
120
+ table a kept run keeps its own run properties, a kept paragraph its own
121
+ paragraph properties and end mark, and the export writes only what the edit
122
+ changed; an uploaded cell's runs read their own family, italic and underline.
123
+ Needs a studio with `SetTableCell.paragraphs`; an older one applies the
124
+ cell's text alone.
125
+ - `cell.text_frame.word_wrap = True` no longer warns: a table cell's text wraps
126
+ at the cell width in PowerPoint and the studio, which is what `True` asks for.
127
+ - `run.text = …` on a table cell holding one run keeps the run, as python-pptx's
128
+ does: only its text changes, and the font, size, colour, bold, italic and link
129
+ it had in the uploaded deck (or that this session wrote) stay, with its
130
+ paragraph's alignment. It retyped the cell (`cell.text`), which dropped every
131
+ property read from the deck: a template's styled cell came out plain. The
132
+ stored run is kept (`SetTableCell.paragraphs`, `keep`) and only its text is
133
+ sent; a studio that predates the field applies the text beside it, which
134
+ keeps the run's style. An uploaded cell's empty run (a template's run
135
+ formatted for text to come) is a run too: given text (or an underline) it
136
+ keeps its properties and link, and `paragraph.add_run()` adds a run after it,
137
+ as python-pptx's does, where the text went in a new, plain run (or in that
138
+ run). On a table the SDK built, the font written on an empty cell's run in an
139
+ earlier session goes with the text it is given, as this session's does.
140
+ - On a table the SDK built, a run's bold or colour written before a family, an
141
+ alignment or italic reads back after a refresh: it read `None`, while the
142
+ studio drew it and the downloaded deck had it.
143
+ - With a studio from this release, `font.bold = False` (`italic`, `underline`)
144
+ on a table the SDK built reaches the downloaded deck as `b="0"` (`i="0"`,
145
+ `u="none"`), as python-pptx writes it: PowerPoint drew a header run turned off
146
+ in the table style's bold, where the studio drew it regular. The fix is in the
147
+ studio's export, so it applies to every SDK version once the studio deploys;
148
+ an uploaded table already wrote them.
149
+ `tests/test_table_cell_formatting_parity.py` compares both with stock
150
+ python-pptx in session, after a refresh and in the exported XML.
151
+ - `shape.shadow.inherit = False` on a shape without a shadow of its own gives
152
+ it python-pptx's empty effect list (`<a:effectLst/>` in the downloaded
153
+ deck), so it casts no shadow, the one its theme effect style casts included,
154
+ and `inherit` reads `False` after the deck is reopened. It changed nothing
155
+ before: PowerPoint kept showing the style's shadow (an Office 2007 theme's
156
+ second effect style, which `add_shape()` names), and a reopened deck read
157
+ `True`. `inherit = True` removes the shape's own shadow and that list, and
158
+ `shadow.visible = False` on a shape with a shadow of its own keeps its
159
+ effect list, emptied, as deleting the `<a:outerShdw>` would (the export
160
+ dropped the list, and PowerPoint drew the style's shadow in its place). An
161
+ uploaded shape with an effect list of its own, an empty `<a:effectLst/>`
162
+ included, reads `inherit == False` as in python-pptx (it read `True`), and
163
+ `inherit = True` removes that list, every effect in it, from the downloaded
164
+ deck. A shadow the shape takes from its style is not reported as its own:
165
+ `inherit` stays `True` and `visible` `False`. Needs a studio with
166
+ `SetShapeShadow.inherit`; an older one ignores the field.
167
+ - `line.dash_style` on an uploaded line reads its `<a:prstDash>` as python-pptx
168
+ 1.0.2 does: `sysDash` is `SQUARE_DOT`, `sysDot` `ROUND_DOT`, `lgDashDotDot`
169
+ `DASH_DOT_DOT` and an explicit `solid` `SOLID`, where the studio's import read
170
+ `sysDash` as `DASH`, `sysDashDot` as `DASH_DOT` and `lgDashDotDot` as no dash
171
+ at all (on a connector, `LONG_DASH_DOT`), and a shape's explicit `solid` as
172
+ `None`. The presets python-pptx has no member for (`dot`, `sysDashDot`,
173
+ `sysDashDotDot`) raise its `ValueError`, as python-pptx does. A dash set in
174
+ the session reads back as the preset the studio writes for it. Needs a
175
+ studio that sends `strokeDashPreset`; with an older one, and for decks
176
+ imported before it, `dash_style` reads as before.
177
+ - `font.fill.type` reads a run's own fill as python-pptx does:
178
+ `MSO_FILL.GRADIENT` for gradient text (`<a:gradFill>`), `MSO_FILL.BACKGROUND`
179
+ for hollow text (`<a:noFill/>`), `MSO_FILL.SOLID` for a colour of its own and
180
+ `None` when the run inherits its fill. It read `None` unless `fill.solid()`
181
+ had been called in the session, and then the string `"solid"`. Setting
182
+ `font.color` on gradient or hollow text makes it solid, as in python-pptx.
183
+ The glyph outline (`<a:ln>`, which python-pptx has no API for) and a
184
+ gradient are carried unchanged when the SDK resends a run. Needs a studio
185
+ that stores `textFill`; with an older one, and for decks imported before it
186
+ until they are imported again, `fill.type` reads as before.
187
+ - `font.fill.background()` makes the text of a run or a paragraph's font
188
+ transparent (`<a:noFill/>`), as python-pptx does: hollow glyphs, drawn by
189
+ their outline alone (an uploaded run's `<a:ln>`) and invisible without one.
190
+ It raised. A colour set afterwards fills the text again, within one batch
191
+ too. Table and chart text still raise. `font.fill.solid()` on hollow or
192
+ gradient text removes that fill, and the text takes the colour it inherits
193
+ until a colour is set (python-pptx writes a colourless `<a:solidFill/>`); it
194
+ only recorded the fill type. Needs a studio that stores `textFill`; an older
195
+ one ignores the writes.
196
+
5
197
  ## 0.18.0 — chart.chart_area / chart.plot_area format and add_chart(name=)
6
198
 
7
199
  - `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.19.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
@@ -828,6 +828,15 @@ print(info['wsUrl'])
828
828
  print(info['authToken'])
829
829
  ```
830
830
 
831
+ People can edit a deck while your code runs, and your writes reach it when
832
+ the batch is sent (in Athena's executor, when the script ends). A write of a
833
+ box's text (`text_frame.text`, `paragraph.text`, `run.text`, …) names the text
834
+ it replaces as the SDK read it, so the studio keeps what someone typed into
835
+ the box after that read: your change goes onto the box's current text. Where
836
+ you both changed the same words, both versions stay, theirs first. Nothing
837
+ changes in the code you write; `text_frame.text` reads what you wrote until
838
+ the deck is read again (`prs.refresh()`).
839
+
831
840
  ---
832
841
 
833
842
  ## Supported Features