athena-python-pptx 0.6.0__tar.gz → 0.8.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/CHANGELOG.md +121 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/CLAUDE.md +4 -2
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/PKG-INFO +1 -1
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/docs/API_PARITY_EXCEPTIONS.md +91 -50
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/__init__.py +1 -1
- athena_python_pptx-0.8.1/pptx/_citations.py +54 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/batching.py +178 -19
- athena_python_pptx-0.8.1/pptx/chart/data.py +446 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/client.py +87 -10
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/commands.py +104 -22
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/dml/color.py +35 -45
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/enum/dml.py +10 -11
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/enum/shapes.py +23 -1
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/enum/text.py +22 -8
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/media.py +37 -5
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/presentation.py +17 -6
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/shapes/__init__.py +364 -77
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/slides.py +95 -45
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/text/__init__.py +217 -78
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/typing.py +1 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/units.py +39 -20
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pyproject.toml +1 -1
- athena_python_pptx-0.6.0/pptx/chart/data.py +0 -266
- athena_python_pptx-0.6.0/uv.lock +0 -1215
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/.gitignore +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/API_PARITY_REPORT.md +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/DEV-GUIDE.md +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/PARITY_QUESTIONS.md +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/PUBLISHING.md +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/README.md +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/docs/athena-api.json +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/docs/athena-api.md +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/_athena_extension.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/_ptc.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/_references.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/action.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/chart/__init__.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/chart/axis.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/chart/category.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/chart/chart.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/chart/datalabel.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/chart/legend.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/chart/marker.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/chart/plot.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/chart/point.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/chart/series.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/chart/xlsx.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/decorators.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/dml/__init__.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/dml/chtfmt.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/dml/effect.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/dml/fill.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/dml/line.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/docgen.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/enum/__init__.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/enum/action.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/enum/chart.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/enum/lang.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/errors.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/exc.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/package.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/parts/__init__.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/parts/_base.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/parts/chart.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/parts/coreprops.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/parts/embeddedpackage.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/parts/image.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/parts/media.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/parts/presentation.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/parts/slide.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/shapes/autoshape.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/shapes/base.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/shapes/connector.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/shapes/freeform.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/shapes/graphfrm.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/shapes/group.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/shapes/picture.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/shapes/placeholder.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/shapes/shapetree.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/shared.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/slide.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/spec.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/table.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/text/fonts.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/text/layout.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/text/text.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/types.py +0 -0
- {athena_python_pptx-0.6.0 → athena_python_pptx-0.8.1}/pptx/util.py +0 -0
|
@@ -2,6 +2,127 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `athena-python-pptx` are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.8.0 — Surgical run text + faithful style hydration
|
|
6
|
+
|
|
7
|
+
Closes the "agent edited deck text and the styling vanished" class of bugs
|
|
8
|
+
(font size/weight/color jumping to the renderer's 18pt default after a text
|
|
9
|
+
rewrite).
|
|
10
|
+
|
|
11
|
+
**`run.text = …` is now surgical.** The setter (and the run text helpers
|
|
12
|
+
`upper/lower/capitalize/title/strip/clear/replace`) emits the new
|
|
13
|
+
`SetRunText` command — the server updates just that run's text, leaving its
|
|
14
|
+
style, hyperlink, sibling runs, and paragraph properties untouched — instead
|
|
15
|
+
of re-serializing the entire text frame from local state. Setting text on a
|
|
16
|
+
freshly `add_run()`-appended run appends server-side inheriting the previous
|
|
17
|
+
run's style (continue-typing semantics). Requires a pptx-studio deployment
|
|
18
|
+
that knows `SetRunText`; set `ATHENA_PPTX_LEGACY_RUN_TEXT=1` to restore the
|
|
19
|
+
old whole-frame re-emit against older servers.
|
|
20
|
+
|
|
21
|
+
**Snapshot hydration is now faithful.** `Run` hydration previously mapped
|
|
22
|
+
only 8 style keys (bold/italic/underline/fontSizePt/fontFamily/fontRef/
|
|
23
|
+
colorHex/spacingPt) — any full-frame re-emit (`paragraph.text`,
|
|
24
|
+
`add_paragraph()`, pre-0.8 `run.text`) silently stripped everything else
|
|
25
|
+
from every run in the shape. Now:
|
|
26
|
+
|
|
27
|
+
- `schemeColor`/`brightness` hydrate into the color API —
|
|
28
|
+
`run.font.color.theme_color` / `.brightness` return the ingested values.
|
|
29
|
+
- `strike`/`strikethrough`, `subscript`/`superscript`/`baseline`, and
|
|
30
|
+
`languageId` hydrate into their typed fields.
|
|
31
|
+
- Everything else the server sends (`cap`, `highlightColorHex`,
|
|
32
|
+
`highlightSchemeColor`, future keys) is carried verbatim in
|
|
33
|
+
`Font._extra_style` and reproduced by re-serialization.
|
|
34
|
+
|
|
35
|
+
Pairs with the server-side `applySetText` preservation upgrade (same PR):
|
|
36
|
+
unchanged lines keep their exact runs, rewritten lines keep the replaced
|
|
37
|
+
line's explicit style, and new lines continue the surrounding style instead
|
|
38
|
+
of collapsing to the 18pt default.
|
|
39
|
+
|
|
40
|
+
## 0.7.1 — CommandBuffer thread-safety
|
|
41
|
+
|
|
42
|
+
Fixes a data-loss/duplication race in `CommandBuffer`. `self._commands` was
|
|
43
|
+
shared between the foreground `add`/`flush`/`clear` path and the 2.0s
|
|
44
|
+
auto-flush daemon thread, with only the timer guarded — so a flush that
|
|
45
|
+
snapshotted-then-reset the buffer could drop a command a concurrent `add`
|
|
46
|
+
appended in between (silent slide/shape loss), and a timer flush racing a
|
|
47
|
+
foreground flush could POST the same commands twice (duplication). In
|
|
48
|
+
long-lived Daytona executor processes this silently dropped or duplicated
|
|
49
|
+
slides/shapes with no error. A dedicated `_commands_lock` now guards every
|
|
50
|
+
read/append/reassign of `_commands` (drain atomically under the lock; POST
|
|
51
|
+
outside it). No public API change. (#24278)
|
|
52
|
+
|
|
53
|
+
## 0.7.0
|
|
54
|
+
|
|
55
|
+
**python-pptx behavioral-parity sweep — read-path return types, enum
|
|
56
|
+
values, and chart-data builder objects now match python-pptx 1.0.2.**
|
|
57
|
+
|
|
58
|
+
Surface parity (every public class/method/signature) was already 100%, but a
|
|
59
|
+
deep-dive against python-pptx 1.0.2 found *behavioral* divergences where
|
|
60
|
+
getters returned raw strings/`None` instead of enum members or typed
|
|
61
|
+
defaults, two enums had wrong integer values, and the chart-data builders
|
|
62
|
+
weren't python-pptx-shaped. All fixes are pure-local (no wire/server change);
|
|
63
|
+
`to_series_payload()` output is byte-identical to before.
|
|
64
|
+
|
|
65
|
+
**Enum value corrections (silent drop-in breakers):**
|
|
66
|
+
|
|
67
|
+
- **`MSO_THEME_COLOR.TEXT_2` / `BACKGROUND_2` were swapped** — now `15` /
|
|
68
|
+
`16` to match upstream (the `<a:schemeClr>` read/write maps were updated in
|
|
69
|
+
lockstep so OOXML output is unchanged). `MSO_THEME_COLOR_INDEX` inherits.
|
|
70
|
+
- **`MSO_TEXT_UNDERLINE_TYPE` had 6 wrong values, 9 missing members, and a
|
|
71
|
+
bogus `DASHED_LINE`** — rewritten to upstream's exact 19 members/values;
|
|
72
|
+
added the `MSO_UNDERLINE` alias (`from pptx.enum.text import MSO_UNDERLINE`).
|
|
73
|
+
- **`PP_MEDIA_TYPE`**: `MOVIE` corrected `2`→`3`, added `OTHER = 1`, and the
|
|
74
|
+
enum is now importable from its canonical `pptx.enum.shapes` location.
|
|
75
|
+
- Added `MSO_SHAPE_TYPE.MIXED = -2` and the `MSO` alias; added the
|
|
76
|
+
upstream-typo `MSO_PATTERN.ERCENT_40` member.
|
|
77
|
+
|
|
78
|
+
**Read-path return-type parity (getters now return enums / typed defaults):**
|
|
79
|
+
|
|
80
|
+
- `FillFormat.type` → `MSO_FILL_TYPE` (was a raw string); `background()` now
|
|
81
|
+
reports `MSO_FILL_TYPE.BACKGROUND`. `gradient_angle`/`gradient_stops`/
|
|
82
|
+
`pattern` raise `TypeError` on a wrong-type fill (parity).
|
|
83
|
+
- `LineFormat.dash_style` → `MSO_LINE_DASH_STYLE` (or `None` when unstyled,
|
|
84
|
+
instead of defaulting to `"solid"`); `LineFormat.width` defaults to
|
|
85
|
+
`Emu(0)` for an unstyled line (a color-/dash-only edit no longer injects a
|
|
86
|
+
synthetic 1pt width).
|
|
87
|
+
- `Chart.chart_type` → `XL_CHART_TYPE` member (was a raw server string);
|
|
88
|
+
`add_chart` now persists `grouping` so stacked/clustered variants read back
|
|
89
|
+
exactly.
|
|
90
|
+
- `TableCell.vertical_anchor` → `MSO_VERTICAL_ANCHOR`; `TableCell.margin_*`
|
|
91
|
+
default to the OOXML defaults (`91440`/`45720` EMU) instead of `None`.
|
|
92
|
+
- `Shape.rotation` is normalized to `[0, 360)` and float-coerced.
|
|
93
|
+
- `_Column.width` / `_Row.height` are populated for `add_table(...)` tables
|
|
94
|
+
(even division, last column/row absorbs the remainder).
|
|
95
|
+
- `RGBColor` is now a real `tuple` subclass (`RGBColor(255,0,0) == (255,0,0)`,
|
|
96
|
+
`isinstance(c, tuple)`).
|
|
97
|
+
- `ColorFormat.theme_color` returns `MSO_THEME_COLOR.NOT_THEME_COLOR` for an
|
|
98
|
+
sRGB color (raises `TypeError` when no color is present) instead of `None`.
|
|
99
|
+
- `TextFrame.auto_size` / `vertical_anchor` return their enums; `margin_*`
|
|
100
|
+
default to the python-pptx `Length` defaults; `Paragraph.line_spacing =
|
|
101
|
+
None` clears the override without crashing; `Font.language_id` returns
|
|
102
|
+
`MSO_LANGUAGE_ID.NONE` on an unset run.
|
|
103
|
+
- `SlideLayouts.get_by_name` is exact/case-sensitive (the fuzzy match moved to
|
|
104
|
+
the Athena-only `by_name`); `Slide.has_notes_slide` flips `True` once
|
|
105
|
+
`notes_slide` is accessed.
|
|
106
|
+
- `Video.ext` returns the bare extension (`'mp4'`, `'vid'` fallback) and
|
|
107
|
+
`Video.filename` synthesizes `'movie.{ext}'`.
|
|
108
|
+
|
|
109
|
+
**`pptx.util` truncation parity:** unit constructors (`Emu`/`Pt`/`Inches`/…)
|
|
110
|
+
truncate toward zero like upstream (`int(Pt(1.9999)) == 25398`) instead of
|
|
111
|
+
rounding; `Length.centipoints` is integer floor-division.
|
|
112
|
+
|
|
113
|
+
**Chart-data builder objects (`pptx.chart.data`) are now python-pptx-shaped:**
|
|
114
|
+
|
|
115
|
+
- `CategoryChartData.add_series(name, values)` returns a real
|
|
116
|
+
`CategorySeriesData` with `.values`, `.add_data_point()`, and `.index`;
|
|
117
|
+
`XyChartData`/`BubbleChartData.add_series` return `XySeriesData`/
|
|
118
|
+
`BubbleSeriesData` whose `add_data_point(...)` returns the data point and
|
|
119
|
+
expose `.x_values` / `.y_values` / `.bubble_sizes`.
|
|
120
|
+
- `CategoryChartData.add_category(label)` and a `Categories`/`Category`
|
|
121
|
+
sequence with `are_dates` / `are_numeric` / `number_format`; category label
|
|
122
|
+
types (dates, numbers) are preserved through `cd.categories[i].label`.
|
|
123
|
+
- The three chart-data containers implement the `Sequence` protocol
|
|
124
|
+
(`for s in chart_data` / `chart_data[0]`).
|
|
125
|
+
|
|
5
126
|
## 0.4.2
|
|
6
127
|
|
|
7
128
|
**Paragraph-level font fidelity — `paragraph.font` now matches stock
|
|
@@ -52,8 +52,10 @@ A small number of REST-SDK-specific departures are documented in
|
|
|
52
52
|
use `slide.notes_slide.notes_text_frame` for notes text.
|
|
53
53
|
- **`XyChartData.add_series(name, values)`** signature drift vs upstream's
|
|
54
54
|
`add_series(name, number_format=None)` — caller passes inline tuples.
|
|
55
|
-
- **`TextFitter.best_fit_font_size()`**
|
|
56
|
-
|
|
55
|
+
- **`TextFitter.best_fit_font_size()`** is server-measured: with a base
|
|
56
|
+
URL configured it queries `POST /measure-text-fit` and returns the
|
|
57
|
+
largest fitting size; only falls back to `max_size` (+ `RuntimeWarning`)
|
|
58
|
+
when no studio client is reachable.
|
|
57
59
|
- **`_BaseSeriesData.index`** raises `NotImplementedError` (raising
|
|
58
60
|
rather than silently returning 0).
|
|
59
61
|
- **`Package.open()`** raises `UnsupportedFeatureError`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: athena-python-pptx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: Drop-in replacement for python-pptx that connects to PPTX Studio for real-time collaboration
|
|
5
5
|
Project-URL: Homepage, https://github.com/pptx-studio/python-sdk
|
|
6
6
|
Project-URL: Documentation, https://docs.pptx-studio.com/sdk/python
|
|
@@ -45,48 +45,51 @@ text = slide.notes_slide.notes_text_frame.text
|
|
|
45
45
|
A future server-side notes-master patch op will plumb the full shape
|
|
46
46
|
tree through. Tracked in `PARITY_QUESTIONS.md`.
|
|
47
47
|
|
|
48
|
-
### `XyChartData.add_series()` —
|
|
48
|
+
### `XyChartData.add_series()` — both the upstream and inline patterns work (v0.7.0)
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
**Our signature:** `add_series(name, values)` — caller passes the
|
|
54
|
-
(x, y) tuples up front.
|
|
55
|
-
|
|
56
|
-
**Migration:**
|
|
50
|
+
As of v0.7.0 `add_series` returns a real `XySeriesData` (resp.
|
|
51
|
+
`BubbleSeriesData`), so the **upstream pattern works**:
|
|
57
52
|
|
|
58
53
|
```python
|
|
59
|
-
# python-pptx pattern (
|
|
54
|
+
# python-pptx pattern (now supported):
|
|
60
55
|
series = data.add_series("Series 1")
|
|
61
|
-
series.add_data_point(1.0, 2.0)
|
|
56
|
+
series.add_data_point(1.0, 2.0) # returns the XyDataPoint
|
|
62
57
|
series.add_data_point(3.0, 4.0)
|
|
58
|
+
series.x_values # [1.0, 3.0]; series.y_values -> [2.0, 4.0]
|
|
63
59
|
|
|
64
|
-
# athena-python-pptx
|
|
60
|
+
# athena-python-pptx inline shorthand (still supported):
|
|
65
61
|
data.add_series("Series 1", [(1.0, 2.0), (3.0, 4.0)])
|
|
66
62
|
```
|
|
67
63
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
`add_series` additionally accepts an optional `number_format=` for
|
|
65
|
+
signature parity. The same applies to `BubbleChartData.add_series`
|
|
66
|
+
(inline triples are `(x, y, size)`).
|
|
71
67
|
|
|
72
|
-
### `TextFitter.best_fit_font_size()` —
|
|
68
|
+
### `TextFitter.best_fit_font_size()` — server-measured (needs a base URL)
|
|
73
69
|
|
|
74
|
-
Upstream binary-searches font sizes locally
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
70
|
+
Upstream binary-searches font sizes against locally-installed font
|
|
71
|
+
metrics, which a REST SDK can't read. When a base URL is configured
|
|
72
|
+
(`ATHENA_PPTX_BASE_URL` or an explicit `client`) the classmethod issues
|
|
73
|
+
a `MeasureTextFit` query to the studio's `POST /measure-text-fit`
|
|
74
|
+
endpoint and returns the largest integer size that fits — measured
|
|
75
|
+
against the studio's bundled fonts via the same layouter the renderer
|
|
76
|
+
uses (see the extension entry below). It falls back to `max_size` with a
|
|
77
|
+
`RuntimeWarning` **only** when no studio client is reachable (offline /
|
|
78
|
+
import-time tests). For full-deck layout, prefer letting the text frame
|
|
79
|
+
auto-fit at render time:
|
|
78
80
|
|
|
79
81
|
```python
|
|
80
82
|
from pptx.enum.text import MSO_AUTO_SIZE
|
|
81
83
|
text_frame.auto_size = MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE
|
|
82
84
|
```
|
|
83
85
|
|
|
84
|
-
### `_BaseSeriesData.index` —
|
|
86
|
+
### `_BaseSeriesData.index` — resolves against the parent chart-data (v0.7.0)
|
|
85
87
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
As of v0.7.0, a series created via `chart_data.add_series(...)` carries a
|
|
89
|
+
back-reference to its parent, so `series.index` returns the series's
|
|
90
|
+
offset within the chart-data exactly like upstream. A *detached* series
|
|
91
|
+
constructed standalone (no parent) still raises `NotImplementedError`
|
|
92
|
+
rather than lying about the offset.
|
|
90
93
|
|
|
91
94
|
### `Package.open(pkg_file)` — raises `UnsupportedFeatureError`
|
|
92
95
|
|
|
@@ -137,6 +140,24 @@ prs.save() # flush only (Athena-only)
|
|
|
137
140
|
prs.save("out.pptx") # flush + export + write to local file (parity)
|
|
138
141
|
```
|
|
139
142
|
|
|
143
|
+
### Oversized flushes are split into multiple atomic sub-transactions
|
|
144
|
+
|
|
145
|
+
The SDK serializes every buffered command into one JSON body and POSTs it
|
|
146
|
+
to `/decks/:id/commands`. pptx-studio's apps/api (Fastify) caps that body
|
|
147
|
+
at 32 MiB, and `AddPicture` / `SubstitutePlaceholder(picture)` carry inline
|
|
148
|
+
base64 image bytes (~4/3 expansion), so a few large images can blow past
|
|
149
|
+
the ceiling. For batches that exceed the limit, `CommandBuffer.flush`
|
|
150
|
+
splits them into multiple atomic sub-transactions (still validating the
|
|
151
|
+
whole batch up front, so an invalid command aborts before anything is
|
|
152
|
+
applied). The all-or-nothing guarantee then holds *per sub-transaction*
|
|
153
|
+
rather than end-to-end — a server-side failure mid-sequence can leave
|
|
154
|
+
earlier chunks committed. The split threshold is tunable via
|
|
155
|
+
`ATHENA_PPTX_MAX_REQUEST_BYTES`; batches within the limit ship as one
|
|
156
|
+
atomic request, unchanged. A single command larger than the budget (one
|
|
157
|
+
very large inline image) cannot be split — it is sent alone and may still
|
|
158
|
+
be rejected with a teaching `IMAGE_TOO_LARGE` error, but it no longer
|
|
159
|
+
takes the rest of the queued edits down with it.
|
|
160
|
+
|
|
140
161
|
---
|
|
141
162
|
|
|
142
163
|
## Agent-Friendly Additions (not in python-pptx)
|
|
@@ -215,34 +236,23 @@ python-pptx's `_Row.height` and `_Column.width` are read/write EMU properties. T
|
|
|
215
236
|
|
|
216
237
|
In python-pptx, `Table.rows` returns a `_RowCollection` of `_Row` objects (each with `.height`). This SDK matches that. In a prior version, `Table.rows` returned a flat collection of cell lists.
|
|
217
238
|
|
|
218
|
-
### `TextFrame.clear()` —
|
|
219
|
-
|
|
220
|
-
In upstream python-pptx, `text_frame.clear()` removes every paragraph except the first, and on that first paragraph it removes every child element **except** `<a:pPr>`. Any bullet definition the author wrote into `<a:pPr>` survives the clear, and an inherited bullet from the master / layout's `<a:lstStyle>` is left to be re-resolved by PowerPoint at render time.
|
|
221
|
-
|
|
222
|
-
This SDK additionally emits a `SetParagraphStyle(paragraph_index=0, bullet='none')` after the empty `SetText`, explicitly overriding the bullet on the cleared paragraph.
|
|
223
|
-
|
|
224
|
-
The deviation exists because pptx-studio **pre-bakes** master/layout list-style inheritance into `richContent.paragraphs[i].bullet` at ingest time (and at slide materialization), then preserves that baked field through every subsequent `applySetText` via `preserveParagraphProps`. Without the explicit override, the python-pptx idiom
|
|
225
|
-
|
|
226
|
-
```python
|
|
227
|
-
tf.clear()
|
|
228
|
-
p = tf.paragraphs[0]
|
|
229
|
-
run = p.add_run()
|
|
230
|
-
run.text = "Title"
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
renders the title with a stray "•" whenever the placeholder lives on a layout whose master defines a level-1 `<a:buChar/>` (the common case for `<p:bodyStyle>` and any content placeholder). The explicit `bullet='none'` clears the baked inheritance so the literal "no pPr was authored" intent reaches the renderer.
|
|
239
|
+
### `TextFrame.clear()` — bullet inheritance (now matches upstream)
|
|
234
240
|
|
|
235
|
-
|
|
241
|
+
`text_frame.clear()` matches upstream python-pptx: it removes the runs but
|
|
242
|
+
preserves the cleared paragraph's inherited list style. A content/body
|
|
243
|
+
placeholder therefore keeps its master `<p:bodyStyle>` bullet, and a title
|
|
244
|
+
keeps its `<p:titleStyle>` `<a:buNone/>` (no bullet), exactly as PowerPoint
|
|
245
|
+
re-resolves them at render time.
|
|
236
246
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
247
|
+
> **Historical note:** earlier versions emitted a
|
|
248
|
+
> `SetParagraphStyle(paragraph_index=0, bullet='none')` after the empty
|
|
249
|
+
> `SetText` to mask a *stray title bullet*. The real cause was incorrect
|
|
250
|
+
> per-type bullet baking — empty layout/master prompt paragraphs were baked
|
|
251
|
+
> with `bullet:'none'` instead of their master `<p:txStyles>` value, and a
|
|
252
|
+
> blanket override then stripped bullets from **content** placeholders too
|
|
253
|
+
> (where bullets are expected). That baking is now correct per placeholder
|
|
254
|
+
> type (`titleStyle` → no bullet, `bodyStyle` → its `<a:buChar/>`), so the
|
|
255
|
+
> override was removed and `clear()` is faithful to upstream.
|
|
246
256
|
|
|
247
257
|
---
|
|
248
258
|
|
|
@@ -328,6 +338,37 @@ img = ws.export_range_as_image_asset("A1:F20", name="Q4 Revenue")
|
|
|
328
338
|
slide.shapes.add_picture(img.url, Inches(1), Inches(1), width=Inches(6))
|
|
329
339
|
```
|
|
330
340
|
|
|
341
|
+
### `Picture.image_url` — owner-scoped media URL for cross-deck image copy
|
|
342
|
+
|
|
343
|
+
python-pptx upstream exposes `Picture.image` returning an `Image` with
|
|
344
|
+
`.blob` bytes. The REST SDK can't materialize bytes locally, so `.image`
|
|
345
|
+
stays `None` (see the omissions list above). **Additionally**, when a
|
|
346
|
+
picture is loaded from a server snapshot it now carries `image_url`: a
|
|
347
|
+
stable `{base_url}/media?key=<assetId>` URL that streams the picture's
|
|
348
|
+
bytes. It returns `None` for pictures with no backing media asset (an
|
|
349
|
+
SDK-added picture not yet flushed/refreshed, a placeholder with no image,
|
|
350
|
+
or exotic sources like OLE/movie posters that lack a top-level asset id).
|
|
351
|
+
|
|
352
|
+
The URL is owner/EDIT-scoped (the `/media` route runs the same deck-access
|
|
353
|
+
check as every other deck op), and `add_picture` auto-attaches the
|
|
354
|
+
sandbox bearer for Athena hosts — so it composes directly into the
|
|
355
|
+
canonical "copy every image from deck A into deck B" recipe:
|
|
356
|
+
|
|
357
|
+
```python
|
|
358
|
+
src = Presentation(asset_id="asset_source")
|
|
359
|
+
dst = Presentation(asset_id="asset_dest")
|
|
360
|
+
for i, slide in enumerate(src.slides):
|
|
361
|
+
for pic in slide.shapes.get_images():
|
|
362
|
+
if pic.image_url:
|
|
363
|
+
dst.slides[i].shapes.add_picture(
|
|
364
|
+
pic.image_url, pic.left, pic.top, pic.width, pic.height
|
|
365
|
+
)
|
|
366
|
+
dst.save()
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
**Portable code that needs to run against stock python-pptx cannot rely
|
|
370
|
+
on `image_url`** — it is an Athena-only accessor.
|
|
371
|
+
|
|
331
372
|
### `name=` kwarg on `add_textbox()` / `add_shape()` / `add_picture()`
|
|
332
373
|
|
|
333
374
|
Optional keyword for setting a stable shape name at creation. python-pptx
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Helpers for SDK-native Athena citations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from urllib.parse import parse_qs, urlparse
|
|
6
|
+
|
|
7
|
+
_ATHENA_SPACES_HOST_SUFFIXES: tuple[str, ...] = (
|
|
8
|
+
"athenaintel.com",
|
|
9
|
+
"athenaintelligence.ai",
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _is_athena_spaces_host(hostname: str | None) -> bool:
|
|
14
|
+
host = (hostname or "").lower()
|
|
15
|
+
return any(
|
|
16
|
+
host == suffix or host.endswith(f".{suffix}")
|
|
17
|
+
for suffix in _ATHENA_SPACES_HOST_SUFFIXES
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def normalize_citation_string(citation_string: str) -> str:
|
|
22
|
+
value = citation_string.strip()
|
|
23
|
+
if not value:
|
|
24
|
+
raise ValueError("citation_string must not be empty")
|
|
25
|
+
|
|
26
|
+
parsed = urlparse(value)
|
|
27
|
+
if parsed.scheme in ("http", "https") and parsed.netloc:
|
|
28
|
+
if parsed.path.rstrip("/") == "/dashboard/spaces":
|
|
29
|
+
if not _is_athena_spaces_host(parsed.hostname):
|
|
30
|
+
raise ValueError(
|
|
31
|
+
"absolute Athena Spaces citation URLs must use an Athena host"
|
|
32
|
+
)
|
|
33
|
+
asset_ids = parse_qs(parsed.query).get("asset_ids")
|
|
34
|
+
if not asset_ids or not asset_ids[0]:
|
|
35
|
+
raise ValueError("Athena Spaces citation URLs must include asset_ids")
|
|
36
|
+
return value
|
|
37
|
+
|
|
38
|
+
if not parsed.scheme and value.startswith("/"):
|
|
39
|
+
relative = urlparse(value)
|
|
40
|
+
if relative.netloc:
|
|
41
|
+
raise ValueError(
|
|
42
|
+
"protocol-relative Athena Spaces citation URLs are not allowed; "
|
|
43
|
+
"use an absolute Athena host"
|
|
44
|
+
)
|
|
45
|
+
if relative.path.rstrip("/") == "/dashboard/spaces":
|
|
46
|
+
asset_ids = parse_qs(relative.query).get("asset_ids")
|
|
47
|
+
if asset_ids and asset_ids[0]:
|
|
48
|
+
return value
|
|
49
|
+
raise ValueError("Athena Spaces citation URLs must include asset_ids")
|
|
50
|
+
|
|
51
|
+
raise ValueError(
|
|
52
|
+
"citation_string must be a serialized citation URL, such as "
|
|
53
|
+
"'/dashboard/spaces/?asset_ids=...' or an 'https://...' URL"
|
|
54
|
+
)
|