athena-python-pptx 0.3.0__tar.gz → 0.4.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 (86) hide show
  1. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/CHANGELOG.md +41 -0
  2. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/PKG-INFO +1 -1
  3. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/docs/API_PARITY_EXCEPTIONS.md +61 -0
  4. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/__init__.py +1 -1
  5. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/client.py +25 -12
  6. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/commands.py +17 -0
  7. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/shapes/__init__.py +626 -16
  8. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/slides.py +96 -0
  9. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pyproject.toml +1 -1
  10. athena_python_pptx-0.4.0/uv.lock +1163 -0
  11. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/.gitignore +0 -0
  12. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/API_PARITY_REPORT.md +0 -0
  13. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/CLAUDE.md +0 -0
  14. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/DEV-GUIDE.md +0 -0
  15. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/PARITY_QUESTIONS.md +0 -0
  16. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/PUBLISHING.md +0 -0
  17. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/README.md +0 -0
  18. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/docs/athena-api.json +0 -0
  19. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/docs/athena-api.md +0 -0
  20. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/_athena_extension.py +0 -0
  21. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/_ptc.py +0 -0
  22. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/_references.py +0 -0
  23. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/action.py +0 -0
  24. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/batching.py +0 -0
  25. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/chart/__init__.py +0 -0
  26. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/chart/axis.py +0 -0
  27. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/chart/category.py +0 -0
  28. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/chart/chart.py +0 -0
  29. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/chart/data.py +0 -0
  30. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/chart/datalabel.py +0 -0
  31. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/chart/legend.py +0 -0
  32. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/chart/marker.py +0 -0
  33. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/chart/plot.py +0 -0
  34. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/chart/point.py +0 -0
  35. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/chart/series.py +0 -0
  36. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/chart/xlsx.py +0 -0
  37. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/decorators.py +0 -0
  38. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/dml/__init__.py +0 -0
  39. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/dml/chtfmt.py +0 -0
  40. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/dml/color.py +0 -0
  41. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/dml/effect.py +0 -0
  42. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/dml/fill.py +0 -0
  43. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/dml/line.py +0 -0
  44. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/docgen.py +0 -0
  45. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/enum/__init__.py +0 -0
  46. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/enum/action.py +0 -0
  47. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/enum/chart.py +0 -0
  48. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/enum/dml.py +0 -0
  49. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/enum/lang.py +0 -0
  50. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/enum/shapes.py +0 -0
  51. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/enum/text.py +0 -0
  52. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/errors.py +0 -0
  53. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/exc.py +0 -0
  54. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/media.py +0 -0
  55. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/package.py +0 -0
  56. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/parts/__init__.py +0 -0
  57. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/parts/_base.py +0 -0
  58. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/parts/chart.py +0 -0
  59. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/parts/coreprops.py +0 -0
  60. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/parts/embeddedpackage.py +0 -0
  61. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/parts/image.py +0 -0
  62. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/parts/media.py +0 -0
  63. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/parts/presentation.py +0 -0
  64. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/parts/slide.py +0 -0
  65. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/presentation.py +0 -0
  66. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/shapes/autoshape.py +0 -0
  67. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/shapes/base.py +0 -0
  68. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/shapes/connector.py +0 -0
  69. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/shapes/freeform.py +0 -0
  70. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/shapes/graphfrm.py +0 -0
  71. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/shapes/group.py +0 -0
  72. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/shapes/picture.py +0 -0
  73. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/shapes/placeholder.py +0 -0
  74. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/shapes/shapetree.py +0 -0
  75. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/shared.py +0 -0
  76. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/slide.py +0 -0
  77. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/spec.py +0 -0
  78. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/table.py +0 -0
  79. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/text/__init__.py +0 -0
  80. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/text/fonts.py +0 -0
  81. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/text/layout.py +0 -0
  82. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/text/text.py +0 -0
  83. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/types.py +0 -0
  84. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/typing.py +0 -0
  85. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/units.py +0 -0
  86. {athena_python_pptx-0.3.0 → athena_python_pptx-0.4.0}/pptx/util.py +0 -0
@@ -2,6 +2,47 @@
2
2
 
3
3
  All notable changes to `athena-python-pptx` are documented in this file.
4
4
 
5
+ ## 0.3.1
6
+
7
+ **Chart title runs, slide insertion, and shape-delete local state — Insight
8
+ Partners session feedback batch.**
9
+
10
+ Investigated [thread_7b324723-…](https://app.athenaintel.com/dashboard/spaces/?session_id=thread_7b324723-a478-4e99-ac8f-6e7a69766292)
11
+ where an agent built a 15-slide LP pitch deck and tripped over three
12
+ SDK rough edges. Each is addressed below.
13
+
14
+ - **`chart.chart_title.text_frame.paragraphs[0].runs[0].font` now works.**
15
+ The `_ChartTitleTextFrame` previously exposed only `text`; iterating
16
+ `paragraphs` raised `AttributeError`, and the agent fell back to
17
+ unstyled titles. The text frame now carries paragraph and run
18
+ proxies; per-run font writes coalesce into a single
19
+ `SetChartTitleRich` patch on the next flush. The simple
20
+ `text_frame.text = "..."` path still uses the cheaper `SetChartTitle`
21
+ patch — no wire-size regression on the no-formatting case.
22
+
23
+ - **`prs.slides.insert(idx, layout)` added.** Previously, placing a new
24
+ slide between two existing slides required `add_slide()` followed by
25
+ `clone(target_index=...)` to shuffle the list — a workaround that
26
+ cloned 11 shapes onto the new slide and then couldn't fully clear
27
+ them. `insert()` emits a single `AddSlide(index=...)` and refreshes
28
+ the local collection.
29
+
30
+ - **`shape.delete()` updates the local `slide.shapes` view immediately.**
31
+ The buffered delete left `len(slide.shapes)` stale, so the
32
+ "Before: 33, After: 33" symptom appeared even when the delete was
33
+ going through correctly server-side. `delete()` now removes the
34
+ shape from the parent `Shapes` collection in addition to queueing
35
+ the `DeleteShape` command. Layout-inherited shapes
36
+ (`source == "layout"`) no-op rather than queue a guaranteed-to-fail
37
+ delete.
38
+
39
+ - **Toolkit prompt updated** to surface the working chart APIs —
40
+ `series.format.fill.solid()` + `series.format.fill.fore_color.rgb`,
41
+ `chart.legend.position`, doughnut/pie chart enums — and the new
42
+ `slide.clear_shapes()` / `prs.slides.insert()` helpers. The
43
+ Insight Partners agent assumed series styling was unsupported and
44
+ skipped per-series brand colors entirely.
45
+
5
46
  ## 0.1.76
6
47
 
7
48
  **Table cell paragraph alignment now round-trips to OOXML (baseline-parity Gap A2).**
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: athena-python-pptx
3
- Version: 0.3.0
3
+ Version: 0.4.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
@@ -830,6 +830,67 @@ The default `fit=None` preserves stock python-pptx behaviour. The only
830
830
  other accepted value is `"contain"`; passing anything else raises
831
831
  `ValueError` to fail loudly rather than silently no-op.
832
832
 
833
+ ## `Shapes.add_athena_anchor()` — Athena-only cross-asset screenshot insert
834
+
835
+ Athena extension with no python-pptx analogue. Renders an Athena
836
+ `Anchor` (a structured reference to a region of another asset — sheet
837
+ cells, sheet ranges, etc.) to a PNG and inserts it as a regular
838
+ `Picture` shape on the current slide. The result is a one-shot raster
839
+ snapshot, **not** a live-linked binding — use `add_linked_table()` /
840
+ `add_linked_ole_object()` for refreshable bindings.
841
+
842
+ ```python
843
+ from pptx._references import AssetReference, SheetRangeAnchor
844
+
845
+ slide.shapes.add_athena_anchor(
846
+ AssetReference(id="asset_20880ea3-95fb-4577-9a2e-ad11b7773939"),
847
+ SheetRangeAnchor(sheet_id=1, range="A1:F20"),
848
+ Inches(1), Inches(1),
849
+ width=Inches(8),
850
+ )
851
+ ```
852
+
853
+ Signature:
854
+
855
+ ```python
856
+ add_athena_anchor(
857
+ source: AssetReference,
858
+ anchor: Anchor,
859
+ left: Length,
860
+ top: Length,
861
+ width: Length | None = None,
862
+ height: Length | None = None,
863
+ *,
864
+ name: str | None = None,
865
+ fit: str | None = None, # "contain" — same semantics as add_picture
866
+ ) -> Shape
867
+ ```
868
+
869
+ Supported anchor types today:
870
+
871
+ | Anchor type | Behaviour |
872
+ |--------------------|----------------------------------------------------------|
873
+ | `SheetCellAnchor` | Renders the single cell as a 1×1 range PNG. |
874
+ | `SheetRangeAnchor` | Renders the A1 range via xlsx-studio's screenshot.png. |
875
+ | `SheetTableAnchor` | Raises `NotImplementedError` (resolve to A1 + retry). |
876
+ | `SlideAnchor` | Raises `NotImplementedError` (no slide-snapshot route). |
877
+ | anything else | Raises `NotImplementedError`. |
878
+
879
+ Sheet anchors route through `GET {ATHENA_XLSX_BASE_URL}/workbooks/:id/screenshot.png`
880
+ with the sandbox's `ATHENA_XLSX_API_KEY` forwarded as a Bearer token —
881
+ xlsx-studio's ownership middleware checks that the calling workspace
882
+ can read the source workbook. Render scale is fixed at 3 (matches
883
+ xlsx-studio's `range-as-asset` default — slide inserts sit at 6–9″
884
+ widths where scale=2 leaves text soft). When `ATHENA_XLSX_BASE_URL` /
885
+ `ATHENA_XLSX_API_KEY` aren't set (e.g. running outside a Daytona
886
+ presentation_exec sandbox), the call raises `ValueError` rather than
887
+ producing a broken picture.
888
+
889
+ For finer control (custom scale, `include_headers`, off-snapshot
890
+ post-processing), fetch the PNG bytes directly via athena-openpyxl's
891
+ `Worksheet.export_range_sync()` and call `add_picture(bytes, …)`
892
+ yourself.
893
+
833
894
  ---
834
895
 
835
896
  ## `Shapes.add_linked_table()` — Athena studio-linking extension (added in Phase 3)
@@ -132,7 +132,7 @@ def flush_all() -> None:
132
132
  _active_buffers[:] = alive
133
133
 
134
134
 
135
- __version__ = "0.3.0"
135
+ __version__ = "0.4.0"
136
136
 
137
137
  __all__ = [
138
138
  # Main entry point
@@ -72,7 +72,11 @@ class Client:
72
72
  base_url: Base URL of the API (e.g., "https://api.pptx-studio.com").
73
73
  If not provided, uses ATHENA_PPTX_BASE_URL environment variable.
74
74
  api_key: Optional API key for authentication.
75
- If not provided, uses ATHENA_PPTX_API_KEY environment variable.
75
+ If not provided, falls back to the ATHENA_PPTX_API_KEY
76
+ environment variable, then to ATHENA_API_KEY (the canonical
77
+ Athena user API key — same value, accepted as a fallback so
78
+ code running in sandboxes that only inject ATHENA_API_KEY
79
+ still authenticates).
76
80
  timeout: Request timeout in seconds
77
81
 
78
82
  Raises:
@@ -83,11 +87,13 @@ class Client:
83
87
  base_url = os.environ.get("ATHENA_PPTX_BASE_URL")
84
88
  if base_url is None:
85
89
  raise ValueError(
86
- "base_url must be provided or ATHENA_PPTX_BASE_URL environment variable must be set"
90
+ "base_url must be provided or the ATHENA_PPTX_BASE_URL "
91
+ "environment variable must be set. Example: "
92
+ 'ATHENA_PPTX_BASE_URL="https://pptx-studio.prd.athenaintel.com".'
87
93
  )
88
94
 
89
95
  if api_key is None:
90
- api_key = os.environ.get("ATHENA_PPTX_API_KEY")
96
+ api_key = os.environ.get("ATHENA_PPTX_API_KEY") or os.environ.get("ATHENA_API_KEY")
91
97
 
92
98
  self.base_url = base_url.rstrip("/")
93
99
  self.api_key = api_key
@@ -129,7 +135,18 @@ class Client:
129
135
  def _handle_response(self, response: requests.Response) -> Any:
130
136
  """Handle API response, raising appropriate errors."""
131
137
  if response.status_code == 401:
132
- raise AuthenticationError("Invalid or expired API key")
138
+ if not self.api_key:
139
+ raise AuthenticationError(
140
+ "No API key was sent. Pass api_key= to Presentation(...) "
141
+ "or set ATHENA_PPTX_API_KEY (or ATHENA_API_KEY as a "
142
+ "fallback) in the environment."
143
+ )
144
+ raise AuthenticationError(
145
+ "Invalid or expired API key. The pptx-studio API rejected the "
146
+ "credentials. Verify ATHENA_PPTX_API_KEY (or the ATHENA_API_KEY "
147
+ "fallback) is set to a valid Athena user API key for the "
148
+ "current workspace."
149
+ )
133
150
 
134
151
  if response.status_code == 409:
135
152
  data = response.json() if response.text else {}
@@ -416,7 +433,7 @@ class Client:
416
433
  if name is None:
417
434
  name = os.path.basename(file_path)
418
435
  # Remove extension
419
- if name.endswith('.pptx') or name.endswith('.potx'):
436
+ if name.endswith(".pptx") or name.endswith(".potx"):
420
437
  name = name[:-5]
421
438
 
422
439
  # Step 1: Create deck to get presigned URL
@@ -496,7 +513,7 @@ class Client:
496
513
  # Use filename as name if not provided
497
514
  if name is None:
498
515
  name = os.path.basename(file_path)
499
- if name.endswith('.pptx') or name.endswith('.potx'):
516
+ if name.endswith(".pptx") or name.endswith(".potx"):
500
517
  name = name[:-5]
501
518
 
502
519
  # Step 1: Create deck to get presigned URL
@@ -800,9 +817,7 @@ class Client:
800
817
  return self._download(download_url)
801
818
 
802
819
  if status["status"] in ("failed", "error"):
803
- raise ExportError(
804
- status.get("error", "Export failed"), job_id
805
- )
820
+ raise ExportError(status.get("error", "Export failed"), job_id)
806
821
 
807
822
  time.sleep(poll_interval)
808
823
 
@@ -870,9 +885,7 @@ class Client:
870
885
  return self._download(image_url)
871
886
 
872
887
  if status["status"] in ("failed", "error"):
873
- raise RenderError(
874
- status.get("error", "Render failed"), job_id
875
- )
888
+ raise RenderError(status.get("error", "Render failed"), job_id)
876
889
 
877
890
  time.sleep(poll_interval)
878
891
 
@@ -1464,6 +1464,23 @@ class SetGradientFill(Command):
1464
1464
  "stops"
1465
1465
  )
1466
1466
 
1467
+ def to_dict(self) -> dict[str, Any]:
1468
+ """Serialize, camelCasing each stop's ``color_hex`` to ``colorHex`` for the server schema."""
1469
+ result = super().to_dict()
1470
+ if self.stops is not None:
1471
+ camel_stops: list[dict[str, Any]] = []
1472
+ for stop in self.stops:
1473
+ camel: dict[str, Any] = {
1474
+ "position": stop.get("position"),
1475
+ "colorHex": stop.get("color_hex"),
1476
+ }
1477
+ transparency = stop.get("transparency")
1478
+ if transparency is not None:
1479
+ camel["transparency"] = transparency
1480
+ camel_stops.append(camel)
1481
+ result["stops"] = camel_stops
1482
+ return result
1483
+
1467
1484
 
1468
1485
  @dataclass
1469
1486
  class SetShapeZOrder(Command):