athena-python-pptx 0.4.3__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/CLAUDE.md +9 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/PKG-INFO +2 -1
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/docs/API_PARITY_EXCEPTIONS.md +102 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/__init__.py +1 -1
- athena_python_pptx-0.6.0/pptx/_references.py +73 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/commands.py +136 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/presentation.py +22 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/shapes/__init__.py +105 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/slides.py +79 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pyproject.toml +6 -1
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/uv.lock +190 -138
- athena_python_pptx-0.4.3/pptx/_references.py +0 -165
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/.gitignore +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/API_PARITY_REPORT.md +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/CHANGELOG.md +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/DEV-GUIDE.md +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/PARITY_QUESTIONS.md +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/PUBLISHING.md +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/README.md +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/docs/athena-api.json +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/docs/athena-api.md +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/_athena_extension.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/_ptc.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/action.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/batching.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/chart/__init__.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/chart/axis.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/chart/category.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/chart/chart.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/chart/data.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/chart/datalabel.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/chart/legend.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/chart/marker.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/chart/plot.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/chart/point.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/chart/series.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/chart/xlsx.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/client.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/decorators.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/dml/__init__.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/dml/chtfmt.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/dml/color.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/dml/effect.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/dml/fill.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/dml/line.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/docgen.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/enum/__init__.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/enum/action.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/enum/chart.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/enum/dml.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/enum/lang.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/enum/shapes.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/enum/text.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/errors.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/exc.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/media.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/package.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/parts/__init__.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/parts/_base.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/parts/chart.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/parts/coreprops.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/parts/embeddedpackage.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/parts/image.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/parts/media.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/parts/presentation.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/parts/slide.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/shapes/autoshape.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/shapes/base.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/shapes/connector.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/shapes/freeform.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/shapes/graphfrm.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/shapes/group.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/shapes/picture.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/shapes/placeholder.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/shapes/shapetree.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/shared.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/slide.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/spec.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/table.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/text/__init__.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/text/fonts.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/text/layout.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/text/text.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/types.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/typing.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/units.py +0 -0
- {athena_python_pptx-0.4.3 → athena_python_pptx-0.6.0}/pptx/util.py +0 -0
|
@@ -69,6 +69,15 @@ local XML / OPC parts:
|
|
|
69
69
|
|
|
70
70
|
`Font.language_id` IS now implemented (returns `MSO_LANGUAGE_ID` enum, accepts enum or int LCID) — added in v0.1.68.
|
|
71
71
|
|
|
72
|
+
### Slide-index conventions
|
|
73
|
+
|
|
74
|
+
Slide indexing has TWO bases — never conflate them:
|
|
75
|
+
|
|
76
|
+
- **0-based** — this SDK (python-pptx parity): `prs.slides[0]`, `slide.slide_index`, and every `slide_index=` parameter are 0-based.
|
|
77
|
+
- **1-based** — the Athena citation/anchor system: `SlideAnchor.slideIndex` / `ShapeAnchor.slideIndex` (in `pptx/_references.py`), the citation methods, and anything displayed as "Slide N" are the 1-based display position.
|
|
78
|
+
|
|
79
|
+
The PPTX Studio server applier converts at the SDK→anchor boundary (`+1`). `slideId` (== the SDK's `slide.slide_id`) is the stable, base-agnostic identity — prefer it when available.
|
|
80
|
+
|
|
72
81
|
### If you need a deviation
|
|
73
82
|
|
|
74
83
|
If there is a genuine technical reason why a deviation from python-pptx is necessary (e.g., the SDK is a REST client and cannot replicate XML-level behavior):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: athena-python-pptx
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.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
|
|
@@ -21,6 +21,7 @@ Classifier: Topic :: Office/Business :: Office Suites
|
|
|
21
21
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
22
|
Classifier: Typing :: Typed
|
|
23
23
|
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: athena-references>=0.1.0
|
|
24
25
|
Requires-Dist: requests>=2.28.0
|
|
25
26
|
Provides-Extra: dev
|
|
26
27
|
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
@@ -1347,3 +1347,105 @@ upstream surface preserved, additions tagged with
|
|
|
1347
1347
|
|
|
1348
1348
|
Total Tier B closures: 16 implemented, 6 audited (already
|
|
1349
1349
|
applicable or N/A by REST architecture).
|
|
1350
|
+
|
|
1351
|
+
---
|
|
1352
|
+
|
|
1353
|
+
## Athena Source Citations — SDK-native (added post-v0.4.3)
|
|
1354
|
+
|
|
1355
|
+
These methods are **additions** to python-pptx, not deviations — python-pptx
|
|
1356
|
+
has no concept of Athena asset references or citations. The user has approved
|
|
1357
|
+
SDK-native citations. They let an agent attach an Athena source citation to a
|
|
1358
|
+
slide or shape; the studio server applies the citation into the deck's
|
|
1359
|
+
`citations` Y.Array as a unified record that is byte-compatible with agora's
|
|
1360
|
+
`build_deck_source_citation`
|
|
1361
|
+
(`agora/agora/web/api/asset/utils/presentations/citation_operations.py`) and the
|
|
1362
|
+
Olympus `Citation` type (`olympus/src/store/citation.types.ts`). So the deck's
|
|
1363
|
+
existing badge + sidebar rendering surfaces agent-authored citations identically
|
|
1364
|
+
to user-authored ones.
|
|
1365
|
+
|
|
1366
|
+
A citation record is:
|
|
1367
|
+
|
|
1368
|
+
```jsonc
|
|
1369
|
+
{
|
|
1370
|
+
"id": "citation_<uuid12>",
|
|
1371
|
+
"destinationAnchor": { "type": "slide", "slideId": "...", "slideIndex": 1 },
|
|
1372
|
+
// or { "type": "shape", "slideId": "...", "slideIndex": 1, "shapeIds": ["..."] }
|
|
1373
|
+
"citation_string": "<source Spaces URL>",
|
|
1374
|
+
"displayValue": "Q4 Sales", // optional
|
|
1375
|
+
"active": true,
|
|
1376
|
+
"created_at": "2026-…T…Z",
|
|
1377
|
+
"created_by": "Athena"
|
|
1378
|
+
}
|
|
1379
|
+
```
|
|
1380
|
+
|
|
1381
|
+
`citation_string` is the serialized source `AssetReference` (whole asset, or
|
|
1382
|
+
asset + sub-anchor) in Spaces-URL form. The SDK leaves it to the server to
|
|
1383
|
+
derive (from `source_ref` + `source_anchor`) so a single canonical serializer
|
|
1384
|
+
produces it.
|
|
1385
|
+
|
|
1386
|
+
### `Slide.add_citation(source, *, anchor=None, display_value=None) -> str`
|
|
1387
|
+
|
|
1388
|
+
Attach a citation to a whole slide. Emits `AddSlideCitation`; the server
|
|
1389
|
+
resolves `slideId` from `slideIndex` and writes a `slide` destination anchor.
|
|
1390
|
+
Returns the created citation id.
|
|
1391
|
+
|
|
1392
|
+
```python
|
|
1393
|
+
from pptx._references import AssetReference, SheetRangeAnchor
|
|
1394
|
+
|
|
1395
|
+
cid = slide.add_citation(
|
|
1396
|
+
AssetReference(id="asset_xlsx_abc123"),
|
|
1397
|
+
anchor=SheetRangeAnchor(sheet_id=0, range="A1:F20"), # optional
|
|
1398
|
+
display_value="Q4 Sales · A1:F20", # optional
|
|
1399
|
+
)
|
|
1400
|
+
```
|
|
1401
|
+
|
|
1402
|
+
### `Shape.add_citation(source, *, anchor=None, display_value=None) -> str`
|
|
1403
|
+
|
|
1404
|
+
Shape-scoped citation — emits `AddShapeCitation` carrying this shape's id, so
|
|
1405
|
+
the server writes a `shape` destination anchor with `shapeIds`. Returns the
|
|
1406
|
+
created citation id. (Mirrors `Slide.add_citation` / `Cell.add_citation` — the
|
|
1407
|
+
citation verb is `add_citation` on every citable object.)
|
|
1408
|
+
|
|
1409
|
+
```python
|
|
1410
|
+
shape = slide.shapes.add_textbox(Inches(1), Inches(1), Inches(4), Inches(1))
|
|
1411
|
+
cid = shape.add_citation(AssetReference(id="asset_doc_xyz"))
|
|
1412
|
+
```
|
|
1413
|
+
|
|
1414
|
+
**Slide-index base shift (citations).** The SDK/command wire `slide_index`
|
|
1415
|
+
(`prs.slides[i]`, `slide.slide_index`) is **0-based** — python-pptx array
|
|
1416
|
+
parity. But the `destinationAnchor.slideIndex` the server writes into the
|
|
1417
|
+
citation record is **1-based** — the "Slide N" display position per the
|
|
1418
|
+
athena-references contract. The server applies the `+1` at the SDK→anchor
|
|
1419
|
+
boundary, so the stored anchor's `slideIndex` is always one greater than the
|
|
1420
|
+
SDK index of the cited slide.
|
|
1421
|
+
|
|
1422
|
+
> ⚠️ If the **cited source** is itself a slide/shape (you pass a
|
|
1423
|
+
> `SlideAnchor` / `ShapeAnchor` as `anchor=`), that anchor's `slide_index` is
|
|
1424
|
+
> the **1-based** display position per the athena-references contract — do
|
|
1425
|
+
> **not** pass the SDK's 0-based `slide.slide_index` into it (add 1).
|
|
1426
|
+
|
|
1427
|
+
### `Slide.remove_citation(citation_id, *, soft=True)` / `Presentation.remove_citation(citation_id, *, soft=True)`
|
|
1428
|
+
|
|
1429
|
+
Remove a citation from the deck by id. Citations are deck-scoped, so both
|
|
1430
|
+
spellings emit the same `RemoveCitation` command — `Slide.remove_citation` is a
|
|
1431
|
+
convenience when an agent already holds a slide. `soft` (default) flips
|
|
1432
|
+
`active` to `False` in place (reversible, readers skip inactive citations);
|
|
1433
|
+
`soft=False` splices the record out of the array.
|
|
1434
|
+
|
|
1435
|
+
```python
|
|
1436
|
+
prs.remove_citation(cid) # soft delete (default)
|
|
1437
|
+
slide.remove_citation(cid, soft=False) # hard splice
|
|
1438
|
+
```
|
|
1439
|
+
|
|
1440
|
+
### `pptx._references` re-homed on the shared `athena-references` package
|
|
1441
|
+
|
|
1442
|
+
`pptx._references` now re-exports the anchor + version-policy dataclasses from
|
|
1443
|
+
the shared `athena-references` package (the single Python source of truth, kept
|
|
1444
|
+
in lockstep with `packages/references` and `agora/agora/utils/asset_references`).
|
|
1445
|
+
The `to_dict()` wire output of the four anchors the module historically exported
|
|
1446
|
+
(`SheetCellAnchor` / `SheetRangeAnchor` / `SheetTableAnchor` / `SlideAnchor`),
|
|
1447
|
+
plus `VersionPolicyLatest` / `VersionPolicyPinned`, is byte-identical to the
|
|
1448
|
+
prior in-module definitions. `AssetReference` remains a thin in-module wrapper
|
|
1449
|
+
that additionally accepts a plain `dict` `meta` (the historical SDK shape) and
|
|
1450
|
+
serializes it verbatim, preserving byte-identical `to_dict()` output for
|
|
1451
|
+
existing callers.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Thin Python mirror of the @athenaintel/references TypeScript package.
|
|
2
|
+
|
|
3
|
+
Used by the linked-OLE / linked-table / citation SDK methods (Athena
|
|
4
|
+
extensions beyond the python-pptx surface). Mirrors only what the SDK needs to
|
|
5
|
+
construct ``AssetReference`` + ``Anchor`` payloads — full reference
|
|
6
|
+
serialization (URI / URL / Spaces format) is server-side.
|
|
7
|
+
|
|
8
|
+
The anchor + version-policy dataclasses are re-exported from the shared
|
|
9
|
+
``athena-references`` package (the single Python source of truth, kept in
|
|
10
|
+
lockstep with ``packages/references`` and ``agora/agora/utils/asset_references``).
|
|
11
|
+
``AssetReference`` is a thin back-compat wrapper that additionally accepts a
|
|
12
|
+
plain ``dict`` ``meta`` (the historical SDK shape) and serializes it verbatim,
|
|
13
|
+
preserving byte-identical ``to_dict()`` output for existing SDK callers.
|
|
14
|
+
|
|
15
|
+
Note (Athena extension):
|
|
16
|
+
This module is NOT part of python-pptx — it exists to construct
|
|
17
|
+
Athena-asset references for the SDK's linked-object / citation methods.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
__athena_extension_module__: bool = True
|
|
23
|
+
__athena_extension_description__: str = (
|
|
24
|
+
"Cross-studio asset reference types (Athena studio-linking, no upstream)."
|
|
25
|
+
)
|
|
26
|
+
__athena_extension_since__: str = "0.1.71"
|
|
27
|
+
|
|
28
|
+
from dataclasses import dataclass
|
|
29
|
+
from typing import Any, Optional
|
|
30
|
+
|
|
31
|
+
from athena_references import (
|
|
32
|
+
Anchor,
|
|
33
|
+
SheetCellAnchor,
|
|
34
|
+
SheetRangeAnchor,
|
|
35
|
+
SheetTableAnchor,
|
|
36
|
+
SlideAnchor,
|
|
37
|
+
VersionPolicy,
|
|
38
|
+
VersionPolicyLatest,
|
|
39
|
+
VersionPolicyPinned,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class AssetReference:
|
|
45
|
+
"""Athena asset reference. ``id`` is the asset UUID.
|
|
46
|
+
|
|
47
|
+
Re-homes the canonical anchor types onto the shared ``athena-references``
|
|
48
|
+
package while preserving the historical SDK contract that ``meta`` may be a
|
|
49
|
+
plain ``dict`` (serialized verbatim) in addition to ``None``.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
id: str
|
|
53
|
+
meta: Optional[dict[str, Any]] = None
|
|
54
|
+
|
|
55
|
+
def to_dict(self) -> dict[str, Any]:
|
|
56
|
+
"""Serialize to the canonical wire shape."""
|
|
57
|
+
out: dict[str, Any] = {"referenced": "asset", "id": self.id}
|
|
58
|
+
if self.meta is not None:
|
|
59
|
+
out["meta"] = dict(self.meta)
|
|
60
|
+
return out
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
__all__ = [
|
|
64
|
+
"AssetReference",
|
|
65
|
+
"SheetCellAnchor",
|
|
66
|
+
"SheetRangeAnchor",
|
|
67
|
+
"SheetTableAnchor",
|
|
68
|
+
"SlideAnchor",
|
|
69
|
+
"Anchor",
|
|
70
|
+
"VersionPolicyLatest",
|
|
71
|
+
"VersionPolicyPinned",
|
|
72
|
+
"VersionPolicy",
|
|
73
|
+
]
|
|
@@ -772,6 +772,139 @@ class AddLinkedTable(Command):
|
|
|
772
772
|
)
|
|
773
773
|
|
|
774
774
|
|
|
775
|
+
@dataclass
|
|
776
|
+
class AddSlideCitation(Command):
|
|
777
|
+
"""Attach an Athena source citation to a whole slide.
|
|
778
|
+
|
|
779
|
+
Records that a deck slide is sourced from another Athena asset. The
|
|
780
|
+
studio server appends a unified citation record to the deck's
|
|
781
|
+
``citations`` Y.Array, byte-compatible with agora's
|
|
782
|
+
``build_deck_source_citation`` and the Olympus ``Citation`` type, so the
|
|
783
|
+
deck's existing badge + sidebar rendering surfaces agent-authored links
|
|
784
|
+
identically to user-authored ones.
|
|
785
|
+
|
|
786
|
+
Note (Athena extension):
|
|
787
|
+
This command is NOT part of python-pptx. See
|
|
788
|
+
``docs/API_PARITY_EXCEPTIONS.md`` for the deviation rationale.
|
|
789
|
+
|
|
790
|
+
Args:
|
|
791
|
+
slide_index: Zero-based index of the cited slide. This stays 0-based on
|
|
792
|
+
the wire (python-pptx array parity); the studio server applier
|
|
793
|
+
resolves the stable ``slideId`` from it and stores the resulting
|
|
794
|
+
``destinationAnchor.slideIndex`` as 1-based (display "Slide N") — do
|
|
795
|
+
not read this wire value as a 1-based position.
|
|
796
|
+
source_ref: Source ``AssetReference`` JSON (``referenced='asset'`` +
|
|
797
|
+
``id``); the asset the slide is sourced from.
|
|
798
|
+
source_anchor: Optional ``Anchor`` JSON within the source (e.g. a
|
|
799
|
+
spreadsheet range); omit to cite the whole asset.
|
|
800
|
+
display_value: Optional human-readable badge label.
|
|
801
|
+
citation_string: Optional pre-serialized source Spaces URL; the
|
|
802
|
+
server derives it from ``source_ref`` + ``source_anchor`` when
|
|
803
|
+
unset.
|
|
804
|
+
client_id: Client-provided ID (optional).
|
|
805
|
+
"""
|
|
806
|
+
|
|
807
|
+
slide_index: int
|
|
808
|
+
source_ref: dict
|
|
809
|
+
source_anchor: Optional[dict] = None
|
|
810
|
+
display_value: Optional[str] = None
|
|
811
|
+
citation_string: Optional[str] = None
|
|
812
|
+
client_id: Optional[str] = None
|
|
813
|
+
|
|
814
|
+
@property
|
|
815
|
+
def command_type(self) -> str:
|
|
816
|
+
return "AddSlideCitation"
|
|
817
|
+
|
|
818
|
+
def validate(self) -> None:
|
|
819
|
+
if self.slide_index < 0:
|
|
820
|
+
raise ValidationError("slide_index must be non-negative", "slide_index")
|
|
821
|
+
if not isinstance(self.source_ref, dict) or not self.source_ref.get("id"):
|
|
822
|
+
raise ValidationError(
|
|
823
|
+
"source_ref must be an AssetReference dict with an 'id' field",
|
|
824
|
+
"source_ref",
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
@dataclass
|
|
829
|
+
class AddShapeCitation(Command):
|
|
830
|
+
"""Attach an Athena source citation to one or more shapes on a slide.
|
|
831
|
+
|
|
832
|
+
Shape-scoped analogue of :class:`AddSlideCitation`. The deck citation's
|
|
833
|
+
``destinationAnchor`` is a ``shape`` anchor carrying ``shapeIds`` so the
|
|
834
|
+
canvas can draw per-shape highlight overlays.
|
|
835
|
+
|
|
836
|
+
Note (Athena extension):
|
|
837
|
+
This command is NOT part of python-pptx. See
|
|
838
|
+
``docs/API_PARITY_EXCEPTIONS.md`` for the deviation rationale.
|
|
839
|
+
|
|
840
|
+
Args:
|
|
841
|
+
slide_index: Zero-based index of the slide the shapes live on. This
|
|
842
|
+
stays 0-based on the wire (python-pptx array parity); the studio
|
|
843
|
+
server applier resolves the stable ``slideId`` from it and stores
|
|
844
|
+
the resulting ``destinationAnchor.slideIndex`` as 1-based (display
|
|
845
|
+
"Slide N") — do not read this wire value as a 1-based position.
|
|
846
|
+
shape_ids: Non-empty list of shape ids on that slide to cite.
|
|
847
|
+
source_ref: Source ``AssetReference`` JSON.
|
|
848
|
+
source_anchor: Optional ``Anchor`` JSON within the source.
|
|
849
|
+
display_value: Optional human-readable badge label.
|
|
850
|
+
citation_string: Optional pre-serialized source Spaces URL.
|
|
851
|
+
client_id: Client-provided ID (optional).
|
|
852
|
+
"""
|
|
853
|
+
|
|
854
|
+
slide_index: int
|
|
855
|
+
shape_ids: list[str]
|
|
856
|
+
source_ref: dict
|
|
857
|
+
source_anchor: Optional[dict] = None
|
|
858
|
+
display_value: Optional[str] = None
|
|
859
|
+
citation_string: Optional[str] = None
|
|
860
|
+
client_id: Optional[str] = None
|
|
861
|
+
|
|
862
|
+
@property
|
|
863
|
+
def command_type(self) -> str:
|
|
864
|
+
return "AddShapeCitation"
|
|
865
|
+
|
|
866
|
+
def validate(self) -> None:
|
|
867
|
+
if self.slide_index < 0:
|
|
868
|
+
raise ValidationError("slide_index must be non-negative", "slide_index")
|
|
869
|
+
if not self.shape_ids:
|
|
870
|
+
raise ValidationError("shape_ids must be non-empty", "shape_ids")
|
|
871
|
+
if not isinstance(self.source_ref, dict) or not self.source_ref.get("id"):
|
|
872
|
+
raise ValidationError(
|
|
873
|
+
"source_ref must be an AssetReference dict with an 'id' field",
|
|
874
|
+
"source_ref",
|
|
875
|
+
)
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
@dataclass
|
|
879
|
+
class RemoveCitation(Command):
|
|
880
|
+
"""Remove a citation from a deck's ``citations`` Y.Array by id.
|
|
881
|
+
|
|
882
|
+
``soft`` (default) flips the record's ``active`` flag to ``False`` in
|
|
883
|
+
place (readers skip inactive citations; Keryx rollback can restore it);
|
|
884
|
+
``soft=False`` splices the record out of the array.
|
|
885
|
+
|
|
886
|
+
Note (Athena extension):
|
|
887
|
+
This command is NOT part of python-pptx. See
|
|
888
|
+
``docs/API_PARITY_EXCEPTIONS.md`` for the deviation rationale.
|
|
889
|
+
|
|
890
|
+
Args:
|
|
891
|
+
citation_id: Id of the citation record to remove.
|
|
892
|
+
soft: Soft-delete (flip ``active``) when ``True`` (default); hard
|
|
893
|
+
splice when ``False``.
|
|
894
|
+
"""
|
|
895
|
+
|
|
896
|
+
citation_id: str
|
|
897
|
+
soft: bool = True
|
|
898
|
+
|
|
899
|
+
@property
|
|
900
|
+
def command_type(self) -> str:
|
|
901
|
+
return "RemoveCitation"
|
|
902
|
+
|
|
903
|
+
def validate(self) -> None:
|
|
904
|
+
if not self.citation_id:
|
|
905
|
+
raise ValidationError("citation_id is required", "citation_id")
|
|
906
|
+
|
|
907
|
+
|
|
775
908
|
@dataclass
|
|
776
909
|
class AddTable(Command):
|
|
777
910
|
"""
|
|
@@ -3098,6 +3231,9 @@ AnyCommand = Union[
|
|
|
3098
3231
|
AddOleObject,
|
|
3099
3232
|
AddLinkedOleObject,
|
|
3100
3233
|
AddLinkedTable,
|
|
3234
|
+
AddSlideCitation,
|
|
3235
|
+
AddShapeCitation,
|
|
3236
|
+
RemoveCitation,
|
|
3101
3237
|
AddTable,
|
|
3102
3238
|
SetTableCell,
|
|
3103
3239
|
SetTableRowCount,
|
|
@@ -871,6 +871,28 @@ class Presentation:
|
|
|
871
871
|
for i, slide in enumerate(self._slides._slides):
|
|
872
872
|
slide._slide_index = i
|
|
873
873
|
|
|
874
|
+
@athena_extension(
|
|
875
|
+
description=(
|
|
876
|
+
"Presentation.remove_citation — remove an Athena source citation "
|
|
877
|
+
"from the deck by id. Not in python-pptx."
|
|
878
|
+
),
|
|
879
|
+
)
|
|
880
|
+
def remove_citation(self, citation_id: str, *, soft: bool = True) -> None:
|
|
881
|
+
"""Remove an Athena source citation from the deck by id.
|
|
882
|
+
|
|
883
|
+
Citations are deck-scoped records in the deck's ``citations`` Y.Array.
|
|
884
|
+
``soft`` (default) flips the record's ``active`` flag to ``False`` in
|
|
885
|
+
place (readers skip inactive citations; Keryx rollback can restore it);
|
|
886
|
+
``soft=False`` splices the record out of the array.
|
|
887
|
+
|
|
888
|
+
Note (Athena extension):
|
|
889
|
+
This method is NOT part of python-pptx. See
|
|
890
|
+
``docs/API_PARITY_EXCEPTIONS.md`` for the deviation rationale.
|
|
891
|
+
"""
|
|
892
|
+
from .commands import RemoveCitation
|
|
893
|
+
|
|
894
|
+
self._buffer.add(RemoveCitation(citation_id=citation_id, soft=soft))
|
|
895
|
+
|
|
874
896
|
@athena_extension(
|
|
875
897
|
issue=1036,
|
|
876
898
|
since="0.1.81",
|
|
@@ -88,6 +88,61 @@ def _is_athena_url(url: str) -> bool:
|
|
|
88
88
|
return any(host == suffix or host.endswith("." + suffix) for suffix in _ATHENA_URL_SUFFIXES)
|
|
89
89
|
|
|
90
90
|
|
|
91
|
+
def _build_citation_wire(
|
|
92
|
+
*,
|
|
93
|
+
source: "AssetReference",
|
|
94
|
+
anchor: Optional["Anchor"],
|
|
95
|
+
) -> tuple[dict, Optional[dict], None]:
|
|
96
|
+
"""Build the ``(source_ref, source_anchor, citation_string)`` wire triple
|
|
97
|
+
for a citation command (Athena extension).
|
|
98
|
+
|
|
99
|
+
Type-checks ``source`` and duck-types the optional ``anchor`` exactly as
|
|
100
|
+
``add_linked_ole_object`` does, then serializes both to their canonical
|
|
101
|
+
wire dicts. ``citation_string`` is left ``None`` so the studio server
|
|
102
|
+
derives the Spaces URL from ``source_ref`` + ``source_anchor`` — keeping a
|
|
103
|
+
single canonical serializer (matching agora's output) rather than a second
|
|
104
|
+
SDK-side one.
|
|
105
|
+
"""
|
|
106
|
+
from .._references import AssetReference
|
|
107
|
+
|
|
108
|
+
if not isinstance(source, AssetReference):
|
|
109
|
+
raise TypeError(
|
|
110
|
+
f"source must be an AssetReference; got {type(source).__name__}",
|
|
111
|
+
)
|
|
112
|
+
source_anchor: Optional[dict] = None
|
|
113
|
+
if anchor is not None:
|
|
114
|
+
if not hasattr(anchor, "to_dict"):
|
|
115
|
+
raise TypeError(
|
|
116
|
+
"anchor must be an Anchor dataclass (SheetRangeAnchor, "
|
|
117
|
+
"SlideAnchor, etc.) — got "
|
|
118
|
+
f"{type(anchor).__name__}",
|
|
119
|
+
)
|
|
120
|
+
source_anchor = anchor.to_dict()
|
|
121
|
+
return source.to_dict(), source_anchor, None
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _add_citation_command(buffer: Optional["CommandBuffer"], cmd: Any) -> str:
|
|
125
|
+
"""Enqueue a citation command and return the created citation id.
|
|
126
|
+
|
|
127
|
+
Reads the id back from the command response
|
|
128
|
+
(``created.citationIds[0]`` / ``created.citationId``) when the buffer
|
|
129
|
+
flushes synchronously; falls back to the command's ``client_id`` (or empty
|
|
130
|
+
string) when the response doesn't carry one (e.g. queued in a batch).
|
|
131
|
+
"""
|
|
132
|
+
if buffer is None:
|
|
133
|
+
return getattr(cmd, "client_id", "") or ""
|
|
134
|
+
response = buffer.add(cmd)
|
|
135
|
+
if response and response.get("created"):
|
|
136
|
+
created = response["created"]
|
|
137
|
+
citation_ids = created.get("citationIds")
|
|
138
|
+
if citation_ids:
|
|
139
|
+
return citation_ids[0]
|
|
140
|
+
citation_id = created.get("citationId")
|
|
141
|
+
if citation_id:
|
|
142
|
+
return citation_id
|
|
143
|
+
return getattr(cmd, "client_id", "") or ""
|
|
144
|
+
|
|
145
|
+
|
|
91
146
|
# Default render scale for xlsx-range screenshots embedded in slides.
|
|
92
147
|
# scale=3 matches xlsx-studio's ``range-as-asset`` default — slide inserts
|
|
93
148
|
# sit at 6–9″ widths, where scale=2 leaves text visibly soft.
|
|
@@ -5899,6 +5954,56 @@ class Shape:
|
|
|
5899
5954
|
if isinstance(by_id, dict):
|
|
5900
5955
|
by_id.pop(self._shape_id, None)
|
|
5901
5956
|
|
|
5957
|
+
def add_citation(
|
|
5958
|
+
self,
|
|
5959
|
+
source: "AssetReference",
|
|
5960
|
+
*,
|
|
5961
|
+
anchor: Optional["Anchor"] = None,
|
|
5962
|
+
display_value: Optional[str] = None,
|
|
5963
|
+
) -> str:
|
|
5964
|
+
"""Attach an Athena source citation to this shape.
|
|
5965
|
+
|
|
5966
|
+
Records that this shape is sourced from another Athena asset. The
|
|
5967
|
+
studio server appends a unified citation record to the deck's
|
|
5968
|
+
``citations`` array (byte-compatible with agora's
|
|
5969
|
+
``build_deck_source_citation`` and the Olympus ``Citation`` type), so
|
|
5970
|
+
the deck's badge + sidebar rendering surfaces it identically to a
|
|
5971
|
+
user-authored link.
|
|
5972
|
+
|
|
5973
|
+
Args:
|
|
5974
|
+
source: Source ``AssetReference`` from ``pptx._references`` — the
|
|
5975
|
+
asset this shape is sourced from.
|
|
5976
|
+
anchor: Optional ``Anchor`` within the source (e.g.
|
|
5977
|
+
``SheetRangeAnchor``); omit to cite the whole asset.
|
|
5978
|
+
display_value: Optional human-readable badge label.
|
|
5979
|
+
|
|
5980
|
+
Returns:
|
|
5981
|
+
The created citation id (``citation_<...>``).
|
|
5982
|
+
|
|
5983
|
+
Raises:
|
|
5984
|
+
TypeError: if ``source`` is not an ``AssetReference`` or ``anchor``
|
|
5985
|
+
isn't an Anchor dataclass.
|
|
5986
|
+
|
|
5987
|
+
Note (Athena extension):
|
|
5988
|
+
This method is NOT part of python-pptx. See
|
|
5989
|
+
``docs/API_PARITY_EXCEPTIONS.md`` for the deviation rationale.
|
|
5990
|
+
"""
|
|
5991
|
+
from ..commands import AddShapeCitation as AddShapeCitationCmd
|
|
5992
|
+
|
|
5993
|
+
source_ref, source_anchor, citation_string = _build_citation_wire(
|
|
5994
|
+
source=source,
|
|
5995
|
+
anchor=anchor,
|
|
5996
|
+
)
|
|
5997
|
+
cmd = AddShapeCitationCmd(
|
|
5998
|
+
slide_index=self._slide.slide_index,
|
|
5999
|
+
shape_ids=[self._shape_id],
|
|
6000
|
+
source_ref=source_ref,
|
|
6001
|
+
source_anchor=source_anchor,
|
|
6002
|
+
display_value=display_value,
|
|
6003
|
+
citation_string=citation_string,
|
|
6004
|
+
)
|
|
6005
|
+
return _add_citation_command(self._buffer, cmd)
|
|
6006
|
+
|
|
5902
6007
|
# -------------------------------------------------------------------------
|
|
5903
6008
|
# Fill and line styling
|
|
5904
6009
|
# -------------------------------------------------------------------------
|
|
@@ -21,6 +21,7 @@ from .typing import DeckSnapshot, ElementSnapshot, SlideId, SlideSnapshot
|
|
|
21
21
|
if TYPE_CHECKING:
|
|
22
22
|
from .batching import CommandBuffer
|
|
23
23
|
from .presentation import Presentation
|
|
24
|
+
from ._references import AssetReference, Anchor
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
def _parse_layout_index(layout_path: Optional[str]) -> Optional[int]:
|
|
@@ -1167,6 +1168,84 @@ class Slide:
|
|
|
1167
1168
|
)
|
|
1168
1169
|
return warnings
|
|
1169
1170
|
|
|
1171
|
+
@athena_only(
|
|
1172
|
+
description=(
|
|
1173
|
+
"Slide.add_citation — attach an Athena source citation to a whole "
|
|
1174
|
+
"slide. Not in python-pptx."
|
|
1175
|
+
),
|
|
1176
|
+
)
|
|
1177
|
+
def add_citation(
|
|
1178
|
+
self,
|
|
1179
|
+
source: "AssetReference",
|
|
1180
|
+
*,
|
|
1181
|
+
anchor: Optional["Anchor"] = None,
|
|
1182
|
+
display_value: Optional[str] = None,
|
|
1183
|
+
) -> str:
|
|
1184
|
+
"""Attach an Athena source citation to this whole slide.
|
|
1185
|
+
|
|
1186
|
+
Records that this slide is sourced from another Athena asset. The
|
|
1187
|
+
studio server appends a unified citation record to the deck's
|
|
1188
|
+
``citations`` array (byte-compatible with agora's
|
|
1189
|
+
``build_deck_source_citation`` and the Olympus ``Citation`` type), so
|
|
1190
|
+
the deck's badge + sidebar rendering surfaces it identically to a
|
|
1191
|
+
user-authored link.
|
|
1192
|
+
|
|
1193
|
+
Args:
|
|
1194
|
+
source: Source ``AssetReference`` from ``pptx._references`` — the
|
|
1195
|
+
asset this slide is sourced from.
|
|
1196
|
+
anchor: Optional ``Anchor`` within the source (e.g.
|
|
1197
|
+
``SheetRangeAnchor``); omit to cite the whole asset.
|
|
1198
|
+
display_value: Optional human-readable badge label.
|
|
1199
|
+
|
|
1200
|
+
Returns:
|
|
1201
|
+
The created citation id (``citation_<...>``).
|
|
1202
|
+
|
|
1203
|
+
Note (Athena extension):
|
|
1204
|
+
This method is NOT part of python-pptx. See
|
|
1205
|
+
``docs/API_PARITY_EXCEPTIONS.md`` for the deviation rationale.
|
|
1206
|
+
"""
|
|
1207
|
+
from .commands import AddSlideCitation
|
|
1208
|
+
from .shapes import _add_citation_command, _build_citation_wire
|
|
1209
|
+
|
|
1210
|
+
source_ref, source_anchor, citation_string = _build_citation_wire(
|
|
1211
|
+
source=source,
|
|
1212
|
+
anchor=anchor,
|
|
1213
|
+
)
|
|
1214
|
+
cmd = AddSlideCitation(
|
|
1215
|
+
slide_index=self._slide_index,
|
|
1216
|
+
source_ref=source_ref,
|
|
1217
|
+
source_anchor=source_anchor,
|
|
1218
|
+
display_value=display_value,
|
|
1219
|
+
citation_string=citation_string,
|
|
1220
|
+
)
|
|
1221
|
+
return _add_citation_command(self._buffer, cmd)
|
|
1222
|
+
|
|
1223
|
+
@athena_only(
|
|
1224
|
+
description=(
|
|
1225
|
+
"Slide.remove_citation — remove a citation from the deck by id. "
|
|
1226
|
+
"Not in python-pptx."
|
|
1227
|
+
),
|
|
1228
|
+
)
|
|
1229
|
+
def remove_citation(self, citation_id: str, *, soft: bool = True) -> None:
|
|
1230
|
+
"""Remove a citation from the deck's ``citations`` array by id.
|
|
1231
|
+
|
|
1232
|
+
``soft`` (default) flips the record's ``active`` flag to ``False`` in
|
|
1233
|
+
place (readers skip inactive citations; Keryx rollback can restore it);
|
|
1234
|
+
``soft=False`` splices the record out of the array.
|
|
1235
|
+
|
|
1236
|
+
Citations are deck-scoped, so this is identical to
|
|
1237
|
+
:meth:`Presentation.remove_citation`; it's exposed on ``Slide`` for
|
|
1238
|
+
convenience when an agent is already holding a slide.
|
|
1239
|
+
|
|
1240
|
+
Note (Athena extension):
|
|
1241
|
+
This method is NOT part of python-pptx. See
|
|
1242
|
+
``docs/API_PARITY_EXCEPTIONS.md`` for the deviation rationale.
|
|
1243
|
+
"""
|
|
1244
|
+
from .commands import RemoveCitation
|
|
1245
|
+
|
|
1246
|
+
if self._buffer is not None:
|
|
1247
|
+
self._buffer.add(RemoveCitation(citation_id=citation_id, soft=soft))
|
|
1248
|
+
|
|
1170
1249
|
@property
|
|
1171
1250
|
def name(self) -> Optional[str]:
|
|
1172
1251
|
"""
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "athena-python-pptx"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.6.0"
|
|
8
8
|
description = "Drop-in replacement for python-pptx that connects to PPTX Studio for real-time collaboration"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -36,6 +36,11 @@ classifiers = [
|
|
|
36
36
|
]
|
|
37
37
|
dependencies = [
|
|
38
38
|
"requests>=2.28.0",
|
|
39
|
+
# Canonical Python source of truth for AssetReference + the Anchor union,
|
|
40
|
+
# re-exported by pptx/_references.py. Deploy follow-up: Dockerfile.prod
|
|
41
|
+
# vendors this wheel via `pip download --no-deps athena-references` (the
|
|
42
|
+
# presentation-exec sandbox has no PyPI access at build time).
|
|
43
|
+
"athena-references>=0.1.0",
|
|
39
44
|
]
|
|
40
45
|
|
|
41
46
|
[project.optional-dependencies]
|