athena-python-pptx 0.1.62__tar.gz → 0.1.63__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 (45) hide show
  1. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/CHANGELOG.md +34 -0
  2. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/PKG-INFO +1 -1
  3. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/__init__.py +1 -1
  4. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/presentation.py +10 -0
  5. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/shapes.py +54 -0
  6. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/slides.py +94 -10
  7. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pyproject.toml +1 -1
  8. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/.gitignore +0 -0
  9. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/API_PARITY_REPORT.md +0 -0
  10. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/CLAUDE.md +0 -0
  11. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/DEV-GUIDE.md +0 -0
  12. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/PUBLISHING.md +0 -0
  13. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/README.md +0 -0
  14. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/docs/API_PARITY_EXCEPTIONS.md +0 -0
  15. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/docs/athena-api.json +0 -0
  16. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/docs/athena-api.md +0 -0
  17. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/parity-tests/.gitignore +0 -0
  18. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/parity-tests/GAP_REPORT.md +0 -0
  19. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/parity-tests/README.md +0 -0
  20. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/parity-tests/_harness.py +0 -0
  21. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/parity-tests/runner.py +0 -0
  22. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/action.py +0 -0
  23. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/batching.py +0 -0
  24. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/chart/__init__.py +0 -0
  25. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/chart/category.py +0 -0
  26. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/chart/data.py +0 -0
  27. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/client.py +0 -0
  28. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/commands.py +0 -0
  29. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/decorators.py +0 -0
  30. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/dml/__init__.py +0 -0
  31. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/dml/color.py +0 -0
  32. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/docgen.py +0 -0
  33. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/enum/__init__.py +0 -0
  34. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/enum/action.py +0 -0
  35. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/enum/chart.py +0 -0
  36. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/enum/dml.py +0 -0
  37. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/enum/shapes.py +0 -0
  38. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/enum/text.py +0 -0
  39. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/errors.py +0 -0
  40. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/slide.py +0 -0
  41. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/table.py +0 -0
  42. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/text.py +0 -0
  43. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/typing.py +0 -0
  44. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/units.py +0 -0
  45. {athena_python_pptx-0.1.62 → athena_python_pptx-0.1.63}/pptx/util.py +0 -0
@@ -2,6 +2,40 @@
2
2
 
3
3
  All notable changes to `athena-python-pptx` are documented in this file.
4
4
 
5
+ ## 0.1.63
6
+
7
+ Theme-hierarchy parity round (originally targeted 0.1.62, but PyPI's 0.1.62
8
+ was claimed by an unrelated upload on 2026-05-06 — bumped to 0.1.63 to keep
9
+ the SDK and PyPI sequence in step). — closes the remaining python-pptx surface gaps
10
+ on `Presentation`, `SlideMaster`, `SlideLayout`, `Slides`, and `SlideShapes`
11
+ that the parity test was allow-listing as KNOWN_MISSING.
12
+
13
+ - **`Presentation.slide_master`** — shorthand for `slide_masters[0]`. Matches
14
+ python-pptx where most single-master decks reach for the canonical master
15
+ via this attribute. Raises `IndexError` if the snapshot has no masters.
16
+ - **`SlideMaster.shapes`** / **`SlideMaster.placeholders`** — return empty
17
+ read-only collections instead of raising `AttributeError`. The REST snapshot
18
+ doesn't materialize master-level shapes as first-class elements (they are
19
+ projected onto each slide during ingestion), but code that probes these
20
+ collections for parity now gets a sensible empty result.
21
+ - **`SlideLayout.shapes`** — same treatment as the master-level collection.
22
+ - **`SlideLayout.slide_master`** — backreference to the parent
23
+ `SlideMaster`. `_build_slide_masters` now threads the master onto each
24
+ child layout when constructing the theme hierarchy, and the backreference
25
+ is `None` only on the legacy hardcoded fallback layouts (used when no
26
+ snapshot is available).
27
+ - **`Slides.get(slide_id, default=None)`** — matches python-pptx's
28
+ dict-style lookup. Same backing store as `Slides.get_by_id`, with the
29
+ added optional default-return semantics.
30
+ - **`SlideShapes.add_group_shape(shapes=())`** — alias that materializes any
31
+ iterable to a list and forwards to `group_shapes(...)`. Consistent with
32
+ python-pptx's signature; the SDK still requires at least 2 shapes per
33
+ group (one `GroupShapes` command per call).
34
+ - **Parity test cleanup** — `tests/test_python_pptx_api_parity.py` no longer
35
+ allow-lists the items above under `KNOWN_MISSING`. Type mismatches for the
36
+ new `@property` accessors are documented in `KNOWN_TYPE_MISMATCHES`
37
+ (functionally identical to python-pptx's `lazyproperty`).
38
+
5
39
  ## 0.1.61
6
40
 
7
41
  Parity round: structural module shims + python-pptx-aligned signatures.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: athena-python-pptx
3
- Version: 0.1.62
3
+ Version: 0.1.63
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
@@ -127,7 +127,7 @@ def flush_all() -> None:
127
127
  _active_buffers[:] = alive
128
128
 
129
129
 
130
- __version__ = "0.1.62"
130
+ __version__ = "0.1.63"
131
131
 
132
132
  __all__ = [
133
133
  # Main entry point
@@ -513,6 +513,16 @@ class Presentation:
513
513
  from .slides import _build_slide_masters
514
514
  return _build_slide_masters(self)
515
515
 
516
+ @property
517
+ def slide_master(self):
518
+ """The first :class:`SlideMaster` (python-pptx parity).
519
+
520
+ Stock python-pptx exposes ``Presentation.slide_master`` as a shortcut
521
+ for ``slide_masters[0]``. Raises ``IndexError`` if the deck has no
522
+ masters in its snapshot.
523
+ """
524
+ return self.slide_masters[0]
525
+
516
526
  def reorder_slides(self, new_order: list[int]) -> None:
517
527
  """
518
528
  Reorder slides in the presentation.
@@ -3915,6 +3915,17 @@ class Shapes:
3915
3915
  # Phase 5: Grouping
3916
3916
  # -------------------------------------------------------------------------
3917
3917
 
3918
+ def add_group_shape(self, shapes=()) -> Shape:
3919
+ """Return a group shape containing ``shapes`` (python-pptx parity).
3920
+
3921
+ Stock python-pptx accepts any iterable of shapes, including an empty
3922
+ one (in which case the group is created and shapes can be added to
3923
+ its child tree later). The REST SDK requires at least two shapes
3924
+ because group creation is a single ``GroupShapes`` command — passing
3925
+ fewer than two raises ``ValueError`` to match :meth:`group_shapes`.
3926
+ """
3927
+ return self.group_shapes(list(shapes))
3928
+
3918
3929
  def group_shapes(self, shapes: list[Shape]) -> Shape:
3919
3930
  """
3920
3931
  Group multiple shapes together into a single group shape.
@@ -4652,11 +4663,19 @@ class _TableCellParagraph:
4652
4663
  the existing per-cell style (``_bold/_font_size_pt/_font_color_hex``).
4653
4664
  Fine for the common header-row formatting use case; richer cells
4654
4665
  will need a real ``<a:txBody>`` model on the server side.
4666
+
4667
+ For API parity with python-pptx, ``add_run()`` and ``clear()`` are
4668
+ supported but operate on the single underlying run — multiple
4669
+ ``add_run()`` calls return the *same* ``_TableCellRun`` object, and
4670
+ only the last text/style assignment wins. ``alignment`` is accepted
4671
+ for compatibility but doesn't yet round-trip to OOXML (no per-cell
4672
+ paragraph-alignment command on the applier).
4655
4673
  """
4656
4674
 
4657
4675
  def __init__(self, cell: "TableCell"):
4658
4676
  self._cell = cell
4659
4677
  self._runs = [_TableCellRun(cell)]
4678
+ self._alignment: Optional[Any] = None
4660
4679
 
4661
4680
  @property
4662
4681
  def runs(self) -> list[_TableCellRun]:
@@ -4670,6 +4689,29 @@ class _TableCellParagraph:
4670
4689
  def text(self, value: str) -> None:
4671
4690
  self._cell.text = value
4672
4691
 
4692
+ @property
4693
+ def alignment(self) -> Optional[Any]:
4694
+ return self._alignment
4695
+
4696
+ @alignment.setter
4697
+ def alignment(self, value: Any) -> None:
4698
+ # Accepted for python-pptx parity; not yet round-tripped to OOXML.
4699
+ self._alignment = value
4700
+
4701
+ def add_run(self) -> _TableCellRun:
4702
+ """Return the single underlying run.
4703
+
4704
+ python-pptx semantics create a new ``_Run`` per call. The cell
4705
+ text-frame shim only supports one run per cell, so every call
4706
+ returns the same ``_TableCellRun`` — text / font assignments on
4707
+ the returned run apply to the cell's existing style fields.
4708
+ """
4709
+ return self._runs[0]
4710
+
4711
+ def clear(self) -> None:
4712
+ """Reset the cell text to empty (compatibility with python-pptx)."""
4713
+ self._cell.text = ""
4714
+
4673
4715
 
4674
4716
  class _TableCellTextFrame:
4675
4717
  """``TextFrame`` adapter for a table cell.
@@ -4677,6 +4719,10 @@ class _TableCellTextFrame:
4677
4719
  Real python-pptx returns a full TextFrame here; we expose a single
4678
4720
  paragraph with a single run so the common ``cell.text_frame.
4679
4721
  paragraphs[0].runs[0].font.bold = True`` recipe works.
4722
+
4723
+ ``add_paragraph()`` returns the same single underlying paragraph for
4724
+ API parity, since the cell shim only supports one paragraph.
4725
+ ``clear()`` empties the cell text.
4680
4726
  """
4681
4727
 
4682
4728
  def __init__(self, cell: "TableCell"):
@@ -4695,6 +4741,14 @@ class _TableCellTextFrame:
4695
4741
  def text(self, value: str) -> None:
4696
4742
  self._cell.text = value
4697
4743
 
4744
+ def add_paragraph(self) -> _TableCellParagraph:
4745
+ """Return the single underlying paragraph (cell shim is single-paragraph)."""
4746
+ return self._paragraphs[0]
4747
+
4748
+ def clear(self) -> None:
4749
+ """Reset the cell text to empty."""
4750
+ self._cell.text = ""
4751
+
4698
4752
 
4699
4753
  class TableCell:
4700
4754
  """
@@ -1179,6 +1179,18 @@ class Slides:
1179
1179
  """Get slide by ID."""
1180
1180
  return self._slides_by_id.get(slide_id)
1181
1181
 
1182
+ def get(
1183
+ self,
1184
+ slide_id: SlideId,
1185
+ default: Optional[Slide] = None,
1186
+ ) -> Optional[Slide]:
1187
+ """Return the slide identified by ``slide_id`` (python-pptx parity).
1188
+
1189
+ Returns ``default`` when no slide matches. Mirrors stock python-pptx's
1190
+ ``Slides.get(slide_id, default=None)``.
1191
+ """
1192
+ return self._slides_by_id.get(slide_id, default)
1193
+
1182
1194
  # -------------------------------------------------------------------------
1183
1195
  # Convenience properties and methods
1184
1196
  # -------------------------------------------------------------------------
@@ -1784,6 +1796,39 @@ class Slides:
1784
1796
  return f"<Slides count={len(self._slides)}>"
1785
1797
 
1786
1798
 
1799
+ class _ReadOnlyShapeCollection:
1800
+ """Empty read-only shape collection for layout/master proxies.
1801
+
1802
+ python-pptx exposes ``slide_layout.shapes`` and ``slide_master.shapes`` as
1803
+ iterable shape collections. The REST snapshot doesn't materialize layout-
1804
+ or master-level shapes as first-class entities, so we surface a
1805
+ consistently-typed empty collection rather than raising ``AttributeError``.
1806
+ Callers that need slide-level shapes should use ``slide.shapes`` instead.
1807
+ """
1808
+
1809
+ def __len__(self) -> int:
1810
+ return 0
1811
+
1812
+ def __iter__(self):
1813
+ return iter(())
1814
+
1815
+ def __getitem__(self, key):
1816
+ raise IndexError("layout/master shapes are not materialized in the REST snapshot")
1817
+
1818
+
1819
+ class _ReadOnlyPlaceholderCollection:
1820
+ """Empty read-only placeholder collection for layout/master proxies."""
1821
+
1822
+ def __len__(self) -> int:
1823
+ return 0
1824
+
1825
+ def __iter__(self):
1826
+ return iter(())
1827
+
1828
+ def __getitem__(self, key):
1829
+ raise KeyError(f"No placeholder with idx={key}")
1830
+
1831
+
1787
1832
  class SlideLayout:
1788
1833
  """
1789
1834
  Slide layout template.
@@ -1824,6 +1869,7 @@ class SlideLayout:
1824
1869
  slide: Optional["Slide"] = None,
1825
1870
  presentation: Optional["Presentation"] = None,
1826
1871
  path: Optional[str] = None,
1872
+ slide_master: Optional["SlideMaster"] = None,
1827
1873
  ):
1828
1874
  self._index = index
1829
1875
  self._name = name
@@ -1836,6 +1882,7 @@ class SlideLayout:
1836
1882
  # `<p:sldLayoutIdLst>` and generally does NOT match the file-name
1837
1883
  # numbering the server otherwise falls back to.
1838
1884
  self._path = path
1885
+ self._slide_master_ref: Optional["SlideMaster"] = slide_master
1839
1886
 
1840
1887
  @property
1841
1888
  def index(self) -> int:
@@ -1898,6 +1945,28 @@ class SlideLayout:
1898
1945
 
1899
1946
  return SlidePlaceholders({})
1900
1947
 
1948
+ @property
1949
+ def shapes(self) -> "_ReadOnlyShapeCollection":
1950
+ """Layout-level shapes (python-pptx parity).
1951
+
1952
+ The REST snapshot does not materialize layout shapes as first-class
1953
+ elements (they are merged into each slide's inherited content during
1954
+ ingestion), so this returns an empty read-only collection. Callers
1955
+ that need to introspect what a layout contributes should iterate
1956
+ ``slide.shapes`` on a slide using the layout instead.
1957
+ """
1958
+ return _ReadOnlyShapeCollection()
1959
+
1960
+ @property
1961
+ def slide_master(self) -> Optional["SlideMaster"]:
1962
+ """The :class:`SlideMaster` this layout belongs to.
1963
+
1964
+ Returns ``None`` if the layout was constructed without a master
1965
+ reference (e.g., from the legacy hardcoded fallback set used when no
1966
+ snapshot is available).
1967
+ """
1968
+ return self._slide_master_ref
1969
+
1901
1970
 
1902
1971
  class SlideLayouts:
1903
1972
  """Collection of slide layouts.
@@ -2028,10 +2097,12 @@ class _MasterSlideLayouts:
2028
2097
  class SlideMaster:
2029
2098
  """A single slide master in the presentation (read-only proxy).
2030
2099
 
2031
- Provides python-pptx-compatible access to ``name`` and ``slide_layouts``.
2032
- Shape and placeholder collections on the master itself are not yet
2033
- materialized via the REST snapshot, so ``shapes`` / ``placeholders`` are
2034
- intentionally not implemented here.
2100
+ Provides python-pptx-compatible access to ``name``, ``slide_layouts``,
2101
+ ``shapes``, and ``placeholders``. Master-level shapes and placeholders
2102
+ are not materialized as first-class elements in the REST snapshot
2103
+ (they are projected onto each slide during ingestion), so ``shapes``
2104
+ and ``placeholders`` return empty read-only collections rather than
2105
+ raising ``AttributeError``.
2035
2106
  """
2036
2107
 
2037
2108
  def __init__(
@@ -2053,6 +2124,16 @@ class SlideMaster:
2053
2124
  def slide_layouts(self) -> _MasterSlideLayouts:
2054
2125
  return self._slide_layouts
2055
2126
 
2127
+ @property
2128
+ def shapes(self) -> _ReadOnlyShapeCollection:
2129
+ """Master-level shapes (python-pptx parity, empty in REST snapshot)."""
2130
+ return _ReadOnlyShapeCollection()
2131
+
2132
+ @property
2133
+ def placeholders(self) -> _ReadOnlyPlaceholderCollection:
2134
+ """Master-level placeholders (python-pptx parity, empty in REST snapshot)."""
2135
+ return _ReadOnlyPlaceholderCollection()
2136
+
2056
2137
  def __repr__(self) -> str:
2057
2138
  return f"<SlideMaster name='{self._name}' layouts={len(self._slide_layouts)}>"
2058
2139
 
@@ -2112,14 +2193,17 @@ def _build_slide_masters(presentation: "Presentation") -> SlideMasters:
2112
2193
  index=global_index,
2113
2194
  name=layout_meta.name,
2114
2195
  presentation=presentation,
2196
+ path=layout_meta.path,
2115
2197
  )
2116
2198
  )
2117
2199
  global_index += 1
2118
- masters.append(
2119
- SlideMaster(
2120
- path=master_data.path,
2121
- name=master_data.name,
2122
- layouts=master_layouts,
2123
- )
2200
+ master = SlideMaster(
2201
+ path=master_data.path,
2202
+ name=master_data.name,
2203
+ layouts=master_layouts,
2124
2204
  )
2205
+ # Backreference layout → master so SlideLayout.slide_master works.
2206
+ for layout in master_layouts:
2207
+ layout._slide_master_ref = master
2208
+ masters.append(master)
2125
2209
  return SlideMasters(masters)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "athena-python-pptx"
7
- version = "0.1.62"
7
+ version = "0.1.63"
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"